C++工作站
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>C++工作站</title>
<!-- Monaco Editor -->
<link rel="stylesheet" data-name="vs/editor/editor.main" href="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/editor/editor.main.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', 'Segoe UI', system-ui, monospace;
background: #1e1e2a;
height: 100vh;
width: 100vw;
overflow: hidden;
position: fixed;
top: 0;
left: 0;
}
.app-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #f5f5f5;
transition: background 0.2s;
overflow: hidden;
}
body.dark .app-container { background: #0c0c14; }
/* 标题栏 */
.title-bar {
background: #e8e8ec;
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #ccc;
cursor: default;
flex-shrink: 0;
}
body.dark .title-bar { background: #14141f; border-bottom-color: #2a2a36; }
.window-title { font-weight: 600; font-size: 14px; color: #333; }
body.dark .window-title { color: #e2e8ff; }
.window-controls { display: flex; gap: 8px; }
.window-btn { background: transparent; border: none; font-size: 16px; cursor: pointer; padding: 0 6px; border-radius: 6px; color: #555; }
body.dark .window-btn { color: #aaa; }
/* 工具栏 */
.toolbar {
background: #e8e8ec;
padding: 6px 16px;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
border-bottom: 1px solid #ccc;
z-index: 20;
flex-shrink: 0;
}
body.dark .toolbar { background: #14141f; border-bottom-color: #2a2a36; }
.menu-group { position: relative; display: inline-block; }
.menu-btn {
background: #ffffff;
border: 1px solid #ccc;
color: #333;
font-size: 13px;
font-weight: 500;
padding: 5px 12px;
border-radius: 8px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
}
body.dark .menu-btn { background: #252532; border-color: #3a3a4a; color: #f0f3ff; }
.menu-btn:hover { background: #d0d0d8; transform: translateY(-1px); }
.dropdown-panel {
position: absolute;
top: 38px;
left: 0;
background: #fff;
border-radius: 12px;
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
min-width: 180px;
z-index: 100;
display: none;
flex-direction: column;
overflow: hidden;
border: 1px solid #ddd;
}
body.dark .dropdown-panel { background: #20202e; border-color: #3b3b52; }
.dropdown-panel.show { display: flex; }
.dropdown-item {
padding: 8px 16px;
color: #333;
font-size: 13px;
cursor: pointer;
border-bottom: 1px solid #eee;
background: #fff;
}
body.dark .dropdown-item { color: #e2e8ff; border-bottom-color: #2e2e42; background: #1e1e2c; }
.dropdown-item:hover { background: #e0e0e8; }
body.dark .dropdown-item:hover { background: #4c51bf; }
.separator { height: 1px; background: #ddd; margin: 4px 0; }
.status {
margin-left: auto;
background: #e0e0e6;
padding: 4px 12px;
border-radius: 40px;
font-size: 11px;
font-family: monospace;
color: #333;
border: 1px solid #ccc;
}
body.dark .status { background: #0a0a12; color: #9ca3f0; border-color: #2c2c3a; }
.zoom-controls {
display: flex;
gap: 6px;
background: #e0e0e6;
padding: 2px 8px;
border-radius: 30px;
}
.zoom-btn {
background: #fff;
border: 1px solid #ccc;
color: #333;
font-weight: bold;
padding: 4px 10px;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
}
body.dark .zoom-btn { background: #2d2d3c; border-color: #3a3a4a; color: white; }
.theme-btn, .chinese-error-btn, .websites-btn, .lang-btn {
background: #fff;
border: 1px solid #ccc;
padding: 5px 12px;
border-radius: 20px;
cursor: pointer;
font-size: 13px;
}
body.dark .theme-btn, body.dark .chinese-error-btn, body.dark .websites-btn, body.dark .lang-btn {
background: #2d2d3c;
border-color: #3a3a4a;
color: white;
}
.lang-btn.active { background: #4c51bf; color: white; border-color: #4c51bf; }
.chinese-error-btn.active, .websites-btn.active { background: #4c51bf; color: white; border-color: #4c51bf; }
.timeout-control {
display: inline-flex;
align-items: center;
gap: 8px;
background: #e0e0e6;
padding: 2px 12px;
border-radius: 30px;
margin-left: 5px;
}
body.dark .timeout-control { background: #1e1e2a; }
.timeout-control label { font-size: 12px; font-weight: 500; }
.timeout-slider { width: 100px; cursor: pointer; }
.timeout-value { font-size: 12px; font-weight: 600; min-width: 40px; }
/* 主布局 */
.main-layout { display: flex; flex: 1; overflow: hidden; position: relative; }
/* 左侧面板 */
.left-panel {
width: 350px;
min-width: 200px;
max-width: 60%;
background: #f8f8fc;
display: flex;
flex-direction: column;
border-right: 1px solid #ddd;
position: relative;
}
body.dark .left-panel { background: #13131f; border-right-color: #2a2a36; }
.left-vertical-container { display: flex; flex-direction: column; flex: 1; overflow: hidden; position: relative; }
.topic-container { flex: 3; display: flex; flex-direction: column; min-height: 80px; border-bottom: 1px solid #ddd; overflow: hidden; }
body.dark .topic-container { border-bottom-color: #2a2a36; }
.filemanager-container { flex: 1; display: flex; flex-direction: column; min-height: 80px; overflow: hidden; }
.right-panel { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; }
.right-vertical-container { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
.code-container { flex: 3; display: flex; flex-direction: column; min-height: 80px; border-bottom: 1px solid #ddd; overflow: hidden; }
body.dark .code-container { border-bottom-color: #2a2a36; }
.run-container { flex: 1; display: flex; flex-direction: column; min-height: 80px; overflow: hidden; }
/* HTML 预览容器 */
.html-preview-container {
flex: 1;
background: #fff;
border-top: 1px solid #ddd;
display: flex;
flex-direction: column;
overflow: hidden;
}
body.dark .html-preview-container { background: #1e1e2a; border-top-color: #2a2a36; }
.preview-header {
background: #e8e8ec;
padding: 4px 12px;
font-size: 12px;
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
body.dark .preview-header { background: #20202e; color: #cddcff; }
#htmlPreview {
flex: 1;
width: 100%;
border: none;
background: white;
}
.topic-header, .filemanager-header, .code-header, .run-header {
background: #e8e8ec; padding: 6px 12px; font-weight: 600; font-size: 13px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; flex-shrink: 0;
}
body.dark .topic-header, body.dark .filemanager-header, body.dark .code-header, body.dark .run-header { background: #20202e; color: #cddcff; }
.topic-content { flex: 1; padding: 12px; overflow-y: auto; display: flex; flex-direction: column; }
#topicTextarea {
width: 100%; flex: 1; background: #fff; border: 1px solid #ccc; border-radius: 12px;
padding: 12px; font-family: monospace; font-size: 14px; resize: none; outline: none;
}
body.dark #topicTextarea { background: #0e0e1a; border-color: #3b3b52; color: #e2e8ff; }
.topic-toolbar { display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end; }
.file-list { flex: 1; overflow-y: auto; padding: 8px; }
.file-item {
padding: 6px 10px; margin: 4px 0; border-radius: 8px; cursor: pointer;
display: flex; justify-content: space-between; background: #fff;
}
body.dark .file-item { background: #1e1e2a; color: #e2e8ff; }
.file-item.active { background: #4c51bf; color: white; }
.add-file-btn { margin: 8px; background: #e0e0e6; border: none; padding: 5px; border-radius: 20px; cursor: pointer; }
#editor { width: 100%; flex: 1; }
.run-content { flex: 1; padding: 8px; display: flex; flex-direction: column; gap: 8px; overflow-y: auto; }
.stdin-area { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.stdin-input {
flex: 2; background: #fff; border: 1px solid #ccc; border-radius: 16px;
padding: 6px 12px; font-family: monospace; font-size: 12px;
}
body.dark .stdin-input { background: #0a0a14; border-color: #3b3b52; color: white; }
.version-select {
background: #fff; border: 1px solid #ccc; border-radius: 20px;
padding: 6px 12px; font-size: 12px; cursor: pointer;
}
body.dark .version-select { background: #2d2d3c; border-color: #3a3a4a; color: white; }
.compile-run-btn { background: #2c7a4d; border: none; color: white; padding: 6px 18px; border-radius: 24px; cursor: pointer; font-weight: 600; }
.cpp-generate-btn { background: #c2410c; border: none; color: white; padding: 6px 18px; border-radius: 24px; cursor: pointer; }
#output {
flex: 1; background: #fafafa; color: #222; font-family: monospace; font-size: 12px;
padding: 12px; overflow-y: auto; white-space: pre-wrap; border-radius: 12px; margin-top: 4px;
}
body.dark #output { background: #0b0b12; color: #eef2ff; }
.resize-handle-vertical {
position: absolute; left: 0; right: 0; height: 6px;
background: transparent; cursor: ns-resize; z-index: 30;
}
.resize-handle-vertical:hover { background: #888; opacity: 0.5; }
.resize-handle-horizontal {
position: absolute; top: 0; bottom: 0; width: 6px;
background: transparent; cursor: ew-resize; z-index: 30;
}
.resize-handle-horizontal:hover { background: #888; opacity: 0.5; }
.websites-sidebar {
width: 0;
background: #ffffff;
border-left: 1px solid #ddd;
overflow: hidden;
transition: width 0.25s ease;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
body.dark .websites-sidebar { background: #151521; border-left-color: #2a2a36; }
.websites-sidebar.open { width: 280px; }
.websites-header {
background: #e8e8ec; padding: 10px 12px; font-weight: 600;
display: flex; justify-content: space-between; align-items: center;
border-bottom: 1px solid #ddd;
}
body.dark .websites-header { background: #20202e; color: #cddcff; border-bottom-color: #2a2a36; }
.websites-list {
flex: 1; overflow-y: auto; padding: 12px;
display: flex; flex-direction: column; gap: 8px;
}
.website-link {
display: block; padding: 8px 12px; background: #f0f0f4;
border-radius: 10px; color: #333; text-decoration: none;
font-size: 13px; transition: 0.1s;
}
body.dark .website-link { background: #1e1e2a; color: #e2e8ff; }
.website-link:hover { background: #4c51bf; color: white; transform: translateX(4px); }
button:active { transform: scale(0.97); }
::-webkit-scrollbar { width: 8px; }
</style>
</head>
<body>
<div class="app-container">
<div class="title-bar"><div class="window-title">⚡C++工作站</div><div class="window-controls"><button id="minimizeHintBtn" class="window-btn">─</button><button id="closeHintBtn" class="window-btn">✕</button></div></div>
<div class="toolbar">
<div class="menu-group"><button class="menu-btn" id="fileMenuBtn">📁 文件</button><div class="dropdown-panel" id="fileDropdown"><div class="dropdown-item" id="newProjectItem">📄 新建项目</div><div class="dropdown-item" id="newFileItem">➕ 新建源文件</div><div class="dropdown-item" id="importFileItem">📂 导入代码</div><div class="dropdown-item" id="exportFileItem">💾 导出当前文件</div><div class="separator"></div><div class="dropdown-item" id="clearOutputItem">🧹 清空输出</div></div></div>
<div class="menu-group"><button class="menu-btn" id="editMenuBtn">✏️ 编辑</button><div class="dropdown-panel" id="editDropdown"><div class="dropdown-item" id="formatCodeItem">✨ 自动格式化</div><div class="dropdown-item" id="removeCommentsItem">🗑️ 去除注释</div><div class="dropdown-item" id="removeBlankLinesItem">📄 删除空白行</div></div></div>
<button id="langToggleBtn" class="lang-btn">🔄 C++模式</button>
<button id="showTopicBtn" class="menu-btn">📖 显示题目</button>
<button id="toggleWebsitesBtn" class="websites-btn">🌐 常用网站</button>
<div class="zoom-controls"><button id="zoomInBtn" class="zoom-btn">A+</button><button id="zoomOutBtn" class="zoom-btn">A-</button><button id="resetZoomBtn" class="zoom-btn">A</button></div>
<button id="themeToggleBtn" class="theme-btn">🌓 深色/浅色</button>
<button id="chineseErrorToggleBtn" class="chinese-error-btn">🌐 中文编译错误(关)</button>
<div class="timeout-control">
<label>⏱️ 编译超时:</label>
<input type="range" id="timeoutSlider" class="timeout-slider" min="1" max="10" step="0.5" value="3">
<span id="timeoutValue" class="timeout-value">3.0 秒</span>
</div>
<div class="status" id="statusMsg">⚡ 就绪 | C++模式 | 可调超时1-10秒</div>
</div>
<div class="main-layout" id="mainLayout">
<!-- 左侧面板 -->
<div class="left-panel" id="leftPanel">
<div class="left-vertical-container" id="leftVerticalContainer">
<div class="topic-container" id="topicContainer" style="display: none; flex: 3;">
<div class="topic-header"><span>📋 题目区</span><span class="collapse-icon" id="collapseTopicBtn">▼</span></div>
<div class="topic-content">
<textarea id="topicTextarea" placeholder="在这里输入题目描述..."></textarea>
<div class="topic-toolbar"><span>字体大小:</span><button id="topicFontMinus">A-</button><button id="topicFontPlus">A+</button></div>
</div>
</div>
<div class="filemanager-container" id="filemanagerContainer" style="flex: 1;">
<div class="filemanager-header"><span>📁 文件管理</span><span class="collapse-icon" id="collapseFileBtn">▼</span></div>
<div class="file-list" id="fileList"></div>
<button id="addFileSidebarBtn" class="add-file-btn">+ 新建文件</button>
</div>
<div class="resize-handle-vertical" id="leftVerticalHandle" style="top: auto; bottom: 0; height: 6px;"></div>
</div>
<div class="resize-handle-horizontal" id="leftRightHandle" style="right: -3px; width: 6px; top: 0; bottom: 0;"></div>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<div class="right-vertical-container" id="rightVerticalContainer">
<div class="code-container" id="codeContainer" style="flex: 3;">
<div class="code-header"><span id="currentFileNameLabel">📄 当前文件: main.cpp</span></div>
<div id="editor"></div>
</div>
<!-- 运行区 / HTML预览区 (动态切换) -->
<div class="run-container" id="runContainer" style="flex: 1;">
<div class="run-header" id="runHeader"><span>🖥️ 运行控制台</span><span class="collapse-icon" id="collapseRunBtn">▼</span></div>
<div id="cppRunContent" class="run-content">
<div class="stdin-area">
<label>⌨️ 输入数据:</label>
<textarea id="stdinInput" class="stdin-input" rows="1" placeholder="标准输入..."></textarea>
<select id="cppStdSelect" class="version-select"><option value="c++11">C++11</option><option value="c++14">C++14</option><option value="c++17" selected>C++17</option></select>
<button id="runBtn" class="compile-run-btn">▶ 编译运行</button>
<button id="generateCppBtn" class="cpp-generate-btn">📄 生成CPP</button>
</div>
<pre id="output">// 输出区域 (C++编译结果)</pre>
</div>
<div id="htmlPreviewContainer" class="html-preview-container" style="display: none;">
<div class="preview-header">
<span>🌐 HTML 实时预览</span>
<button id="refreshPreviewBtn" style="background:none; border:none; cursor:pointer;">🔄 刷新</button>
</div>
<iframe id="htmlPreview" title="HTML Preview" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe>
</div>
</div>
<div class="resize-handle-vertical" id="rightVerticalHandle" style="top: auto; bottom: 0; height: 6px;"></div>
</div>
</div>
<!-- 常用网站侧边栏 -->
<div class="websites-sidebar" id="websitesSidebar">
<div class="websites-header"><span>🔗 常用网站</span><button id="closeWebsitesBtn" style="background:none; border:none; cursor:pointer;">✕</button></div>
<div class="websites-list">
<a href="https://www.deepseek.com/" target="_blank" class="website-link">DeepSeek</a>
<a href="https://www.lanzoui.com/" target="_blank" class="website-link">蓝奏云</a>
<a href="https://www.ilanzou.com/" target="_blank" class="website-link">蓝奏云优享版</a>
<a href="https://www.bcoi.cn/" target="_blank" class="website-link">BCOI</a>
<a href="https://www.luogu.com.cn/" target="_blank" class="website-link">洛谷</a>
<a href="https://basic.smartedu.cn/" target="_blank" class="website-link">国家教育平台</a>
<a href="https://www.ccf.org.cn/" target="_blank" class="website-link">中国计算机学会</a>
<a href="https://excalidraw.com/" target="_blank" class="website-link">小拨鼠 (Excalidraw)</a>
<a href="https://easychuan.cn/" target="_blank" class="website-link">传文件</a>
<a href="https://ceic.kpcb.org.cn/cms/" target="_blank" class="website-link">全国青少年信息素养大赛</a>
<a href="https://cppreference.cn/w/" target="_blank" class="website-link">C++官网</a>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
// ---------- 项目数据 ----------
let projects = []; let activeFileId = null; let nextFileId = 1;
let currentCppStd = "c++17"; let editorFontSize = 13; let editor = null; let monacoReady = false;
let isDarkTheme = false; let isCompiling = false; let topicFontSize = 14;
let chineseErrorEnabled = false;
let websitesOpen = false;
let compileTimeoutSec = 3.0;
let currentLanguage = "cpp"; // "cpp" 或 "html"
const defaultCpp = `#include <iostream>\nusing namespace std;\nint main() {\n cout << "Hello, C++!" << endl;\n return 0;\n}`;
const defaultHtml = `<!DOCTYPE html>\n<html>\n<head>\n <meta charset="UTF-8">\n <title>我的页面</title>\n <style>\n body { font-family: Arial; text-align: center; padding: 50px; }\n h1 { color: #4c51bf; }\n </style>\n</head>\n<body>\n <h1>欢迎使用HTML编辑器</h1>\n <p>直接编写HTML,右侧实时预览</p>\n <button onclick="alert('Hello!')">点击测试</button>\n</body>\n</html>`;
function saveData() {
localStorage.setItem('cpp_app_data', JSON.stringify({
projects: projects.map(p=>({id:p.id,name:p.name,content:p.content})),
activeFileId, nextFileId, currentCppStd, editorFontSize,
theme:isDarkTheme?'dark':'light',
topicContent: document.getElementById('topicTextarea').value,
topicFontSize, chineseErrorEnabled, websitesOpen, compileTimeoutSec, currentLanguage
}));
}
function loadData() {
const s = localStorage.getItem('cpp_app_data');
if(!s){ initProject("默认项目"); document.getElementById('topicTextarea').value = "题目描述区域"; return; }
try{
const d=JSON.parse(s);
projects=d.projects.map(p=>({...p})); activeFileId=d.activeFileId; nextFileId=d.nextFileId;
currentCppStd=d.currentCppStd||"c++17"; editorFontSize=d.editorFontSize||13;
if(d.theme==='dark'){ isDarkTheme=true; document.body.classList.add('dark'); }else{ isDarkTheme=false; document.body.classList.remove('dark'); }
if(d.topicContent) document.getElementById('topicTextarea').value = d.topicContent;
if(d.topicFontSize) topicFontSize = d.topicFontSize;
if(d.chineseErrorEnabled !== undefined) chineseErrorEnabled = d.chineseErrorEnabled;
if(d.websitesOpen !== undefined) websitesOpen = d.websitesOpen;
if(d.compileTimeoutSec !== undefined) compileTimeoutSec = d.compileTimeoutSec;
if(d.currentLanguage !== undefined) currentLanguage = d.currentLanguage;
document.getElementById('topicTextarea').style.fontSize = topicFontSize+'px';
const slider = document.getElementById('timeoutSlider');
if(slider) { slider.value = compileTimeoutSec; document.getElementById('timeoutValue').innerText = compileTimeoutSec.toFixed(1) + " 秒"; }
updateChineseErrorButton();
renderFileList();
if(monacoReady&&editor){
const a=projects.find(f=>f.id===activeFileId);
if(a){
editor.setValue(a.content);
document.getElementById('currentFileNameLabel').innerText=`📄 当前文件: ${a.name}`;
editor.updateOptions({fontSize:editorFontSize});
// 根据文件扩展名设置语言模式
if(a.name.endsWith('.html')) setLanguageMode('html', false);
else setLanguageMode('cpp', false);
}
applyTheme();
}
if(websitesOpen) toggleWebsites(true);
// 同步UI语言按钮
document.getElementById('langToggleBtn').textContent = currentLanguage === 'cpp' ? '🔄 HTML模式' : '🔄 C++模式';
updateUIBasedOnLanguage();
}catch(e){ initProject("默认项目"); }
}
function updateUIBasedOnLanguage() {
const cppRun = document.getElementById('cppRunContent');
const htmlPreview = document.getElementById('htmlPreviewContainer');
const runHeader = document.getElementById('runHeader');
if(currentLanguage === 'cpp') {
cppRun.style.display = 'flex';
htmlPreview.style.display = 'none';
if(runHeader) runHeader.innerHTML = '<span>🖥️ 运行控制台 (C++)</span><span class="collapse-icon">▼</span>';
} else {
cppRun.style.display = 'none';
htmlPreview.style.display = 'flex';
if(runHeader) runHeader.innerHTML = '<span>🌐 HTML 实时预览</span><span class="collapse-icon">▼</span>';
refreshHtmlPreview();
}
}
function refreshHtmlPreview() {
if(currentLanguage !== 'html') return;
const code = editor.getValue();
const previewFrame = document.getElementById('htmlPreview');
if(previewFrame) {
const blob = new Blob([code], {type: 'text/html'});
const url = URL.createObjectURL(blob);
previewFrame.src = url;
setTimeout(() => URL.revokeObjectURL(url), 100);
}
}
function setLanguageMode(lang, saveToStorage=true) {
currentLanguage = lang;
const btn = document.getElementById('langToggleBtn');
btn.textContent = currentLanguage === 'cpp' ? '🔄 HTML模式' : '🔄 C++模式';
// 更改当前文件扩展名和编辑器语言
const currentFile = projects.find(f=>f.id===activeFileId);
if(currentFile) {
let newName = currentFile.name;
if(currentLanguage === 'cpp' && !newName.endsWith('.cpp')) newName = newName.replace(/\.html$/, '') + '.cpp';
if(currentLanguage === 'html' && !newName.endsWith('.html')) newName = newName.replace(/\.cpp$/, '') + '.html';
if(newName !== currentFile.name) {
currentFile.name = newName;
document.getElementById('currentFileNameLabel').innerText = `📄 当前文件: ${newName}`;
renderFileList();
}
// 设置编辑器语言
monaco.editor.setModelLanguage(editor.getModel(), currentLanguage === 'cpp' ? 'cpp' : 'html');
// 如果内容为空或默认,设置默认内容
if(!currentFile.content.trim() || currentFile.content === defaultCpp || currentFile.content === defaultHtml) {
const newContent = currentLanguage === 'cpp' ? defaultCpp : defaultHtml;
currentFile.content = newContent;
editor.setValue(newContent);
}
}
updateUIBasedOnLanguage();
if(saveToStorage) saveData();
}
function toggleLanguage() {
setLanguageMode(currentLanguage === 'cpp' ? 'html' : 'cpp', true);
updateStatus(`已切换到${currentLanguage === 'cpp' ? 'C++' : 'HTML'}模式`);
}
function updateChineseErrorButton() { const btn = document.getElementById('chineseErrorToggleBtn'); if(btn) { btn.textContent = chineseErrorEnabled ? "🌐 中文编译错误(开)" : "🌐 中文编译错误(关)"; if(chineseErrorEnabled) btn.classList.add('active'); else btn.classList.remove('active'); } }
function toggleChineseError() { chineseErrorEnabled = !chineseErrorEnabled; updateChineseErrorButton(); saveData(); updateStatus(chineseErrorEnabled ? "中文错误翻译已开启" : "中文错误翻译已关闭"); }
function initProject(name){ projects=[]; const id=nextFileId++; projects.push({id,name:"main.cpp",content:defaultCpp}); activeFileId=id; renderFileList(); if(monacoReady&&editor){ editor.setValue(defaultCpp); editor.updateOptions({fontSize:editorFontSize}); document.getElementById('currentFileNameLabel').innerText="📄 当前文件: main.cpp"; setLanguageMode('cpp', false); } saveData(); }
function renderFileList(){ const c=document.getElementById('fileList'); if(!c) return; c.innerHTML=''; projects.forEach(f=>{ const div=document.createElement('div'); div.className='file-item'+(activeFileId===f.id?' active':''); div.innerHTML=`<span class="file-name">${escapeHtml(f.name)}</span><button class="delete-file" data-id="${f.id}">🗑️</button>`; div.addEventListener('click',(e)=>{ if(!e.target.classList.contains('delete-file')) switchFile(f.id); }); div.querySelector('.delete-file').addEventListener('click',(e)=>{ e.stopPropagation(); delFile(f.id); }); c.appendChild(div); }); const a=projects.find(f=>f.id===activeFileId); if(a) document.getElementById('currentFileNameLabel').innerText=`📄 当前文件: ${a.name}`; }
function escapeHtml(s){ return s.replace(/[&<>]/g,m=>({'&':'&','<':'<','>':'>'}[m])); }
function switchFile(id){ if(!monacoReady||!editor) return; const cur=projects.find(f=>f.id===activeFileId); if(cur) cur.content=editor.getValue(); activeFileId=id; const t=projects.find(f=>f.id===activeFileId); if(t){ editor.setValue(t.content); document.getElementById('currentFileNameLabel').innerText=`📄 当前文件: ${t.name}`; const isHtml = t.name.endsWith('.html'); setLanguageMode(isHtml ? 'html' : 'cpp', false); } renderFileList(); saveData(); }
function delFile(id){ if(projects.length<=1){ updateStatus("至少保留一个文件",true); return; } const was=(activeFileId===id); projects=projects.filter(f=>f.id!==id); if(was){ activeFileId=projects[0].id; if(monacoReady&&editor){ editor.setValue(projects[0].content); document.getElementById('currentFileNameLabel').innerText=`📄 当前文件: ${projects[0].name}`; setLanguageMode(projects[0].name.endsWith('.html')?'html':'cpp', false); } } renderFileList(); saveData(); }
function addNew(){ let name=prompt("文件名:","new.cpp"); if(!name) return; if(!name.match(/\.(cpp|h|hpp|c|html)$/)) name+=".cpp"; if(projects.some(f=>f.name===name)){ updateStatus("已存在",true); return; } const id=nextFileId++; const defaultContent = name.endsWith('.html') ? defaultHtml : defaultCpp; projects.push({id,name,content:defaultContent}); switchFile(id); saveData(); }
function newProj(){ let pn=prompt("项目名:","MyProject"); if(pn) initProject(pn); }
function exportCur(){ const a=projects.find(f=>f.id===activeFileId); let code=a?a.content:(editor?editor.getValue():""); const blob=new Blob([code],{type:'text/plain'}); const link=document.createElement('a'); link.href=URL.createObjectURL(blob); link.download=a?a.name:"code"; link.click(); URL.revokeObjectURL(link.href); updateStatus("已导出"); }
function importFile(file){ if(!file) return; const r=new FileReader(); r.onload=e=>{ if(editor){ editor.setValue(e.target.result); const act=projects.find(f=>f.id===activeFileId); if(act) act.content=e.target.result; saveData(); updateStatus(`导入 ${file.name}`); const isHtml = file.name.endsWith('.html'); if(isHtml) setLanguageMode('html', false); else setLanguageMode('cpp', false); } }; r.readAsText(file); }
function clearOut(){ document.getElementById('output').innerText="// 输出已清空"; updateStatus("清空输出"); }
function updateStatus(msg,err=false){ const el=document.getElementById('statusMsg'); if(el){ el.innerHTML=msg; el.style.color=err?'#ff6b6b':'inherit'; setTimeout(()=>{if(el.innerHTML===msg)el.style.color='inherit';},2500); } }
function setFont(delta){ let s=editorFontSize+delta; if(s<10)s=10; if(s>28)s=28; editorFontSize=s; if(editor) editor.updateOptions({fontSize:editorFontSize}); saveData(); updateStatus(`字体 ${editorFontSize}px`); }
function resetFont(){ editorFontSize=13; if(editor) editor.updateOptions({fontSize:13}); saveData(); updateStatus("字体重置"); }
function toggleTheme(){ isDarkTheme=!isDarkTheme; if(isDarkTheme) document.body.classList.add('dark'); else document.body.classList.remove('dark'); if(editor&&monacoReady) monaco.editor.setTheme(isDarkTheme?'vs-dark':'vs'); saveData(); updateStatus(isDarkTheme?"深色":"浅色"); }
function applyTheme(){ if(editor&&monacoReady) monaco.editor.setTheme(isDarkTheme?'vs-dark':'vs'); }
function formatCode(){ if(!editor) return; const code=editor.getValue(); const formatted=formatCpp(code); editor.setValue(formatted); const act=projects.find(f=>f.id===activeFileId); if(act) act.content=formatted; saveData(); updateStatus("格式化完成"); }
function formatCpp(code){ const lines=code.split(/\r?\n/); let indent=0,unit=" ",out=[]; for(let l of lines){ let t=l.trim(); if(t===""){ out.push(""); continue; } let cur=indent; if(t.startsWith('}')||t.startsWith('};')) cur=Math.max(0,indent-1); let fmt=unit.repeat(cur)+t; if(t.endsWith('{')&&!t.endsWith('{{')) indent++; if((t.startsWith('}')||t.startsWith('};'))&&indent>0) indent--; out.push(fmt); } return out.join('\n').replace(/\n\s*\n\s*\n/g,'\n\n'); }
function removeCommentsFunc(){ if(!editor) return; const code=editor.getValue(); const cleaned=removeCommentsCpp(code); editor.setValue(cleaned); const act=projects.find(f=>f.id===activeFileId); if(act) act.content=cleaned; saveData(); updateStatus("去除注释"); }
function removeCommentsCpp(code){ let out="",inLine=false,inBlock=false,inStr=false,inChar=false,esc=false; for(let i=0;i<code.length;i++){ let ch=code[i],nxt=i+1<code.length?code[i+1]:'\0'; if(!inLine&&!inBlock){ if(inStr&&ch==='\\'&&!esc){esc=true; out+=ch; continue;} if(inStr&&ch==='"'&&!esc){inStr=false; out+=ch; continue;} if(inChar&&ch==='\''&&!esc){inChar=false; out+=ch; continue;} if(inStr||inChar){out+=ch; esc=false; continue;} if(ch==='"'){inStr=true; out+=ch; continue;} if(ch==='\''){inChar=true; out+=ch; continue;} } if(esc&&!inStr&&!inChar) esc=false; if(!inBlock&&!inLine&&ch==='/'&&nxt==='/'){inLine=true; i++; continue;} if(!inBlock&&!inLine&&ch==='/'&&nxt==='*'){inBlock=true; i++; continue;} if(inLine&&(ch==='\n'||ch==='\r')){inLine=false; out+=ch; continue;} if(inBlock&&ch==='*'&&nxt==='/'){inBlock=false; i++; continue;} if(!inLine&&!inBlock) out+=ch; esc=false; } return out; }
function removeBlank(){ if(!editor) return; const code=editor.getValue(); const lines=code.split(/\r?\n/); const newLines=lines.filter(l=>l.trim().length>0); editor.setValue(newLines.join('\n')); const act=projects.find(f=>f.id===activeFileId); if(act) act.content=newLines.join('\n'); saveData(); updateStatus("删除空白行"); }
async function compileRun() {
if(currentLanguage !== 'cpp') { updateStatus("当前为HTML模式,请切换到C++模式编译", true); return; }
if(isCompiling) return;
let code = editor.getValue();
if(!code.trim()) code = "int main(){}";
const stdin = document.getElementById('stdinInput').value;
let stdFlag = currentCppStd === "c++11" ? "-std=c++11" : (currentCppStd === "c++14" ? "-std=c++14" : "-std=c++17");
const outDiv = document.getElementById('output');
const btn = document.getElementById('runBtn');
const timeoutMs = compileTimeoutSec * 1000;
outDiv.innerText = `⏳ 编译中... (超时: ${compileTimeoutSec}秒)`;
updateStatus(`编译中 (超时${compileTimeoutSec}秒)...`);
isCompiling = true;
btn.disabled = true;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch('https://wandbox.org/api/compile.json', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ compiler: "gcc-head", code, options: "Warning,-Wall", compiler_option_raw: `${stdFlag} -O2`, stdin: stdin || "", save: false }),
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
let outText = "";
if (data.compiler_error && data.compiler_error.trim()) {
let errMsg = data.compiler_error;
if (chineseErrorEnabled) errMsg = translateCompileError(errMsg);
outText = "❌ 编译错误:\n" + errMsg;
} else if (data.compiler_message && data.compiler_message.trim()) {
let warnMsg = data.compiler_message;
if (chineseErrorEnabled) warnMsg = translateCompileError(warnMsg);
outText = "⚠️ 编译警告:\n" + warnMsg + "\n\n📦 程序输出:\n" + (data.program_message || "(无输出)");
} else {
outText = data.program_message || "✅ 编译成功,无输出内容。";
}
outDiv.innerText = outText;
updateStatus(`编译完成 ✓`);
} catch (err) {
let errorDetail = err.name === 'AbortError' ? `编译超时(>${compileTimeoutSec}秒)` : `编译请求失败: ${err.message}`;
outDiv.innerText = `❌ 编译失败: ${errorDetail}\n提示: 可在工具栏调整超时时间。`;
updateStatus("编译失败", true);
} finally { isCompiling = false; btn.disabled = false; }
}
function translateCompileError(txt){ const map=[{en:/'([^']+)' was not declared in this scope/gi,zh:'“$1”未在此作用域中声明'},{en:/expected ';' before '([^']+)'/gi,zh:'在“$1”前缺少分号'}]; let r=txt; for(let m of map) r=r.replace(m.en,m.zh); return r; }
function genCpp(){ if(currentLanguage !== 'cpp') { updateStatus("当前为HTML模式,请切换到C++模式生成CPP", true); return; } const act=projects.find(f=>f.id===activeFileId); let code=act?act.content:editor.getValue(); let fn=act?act.name:"code.cpp"; if(!fn.endsWith(".cpp")) fn+=".cpp"; const blob=new Blob([code],{type:'text/plain'}); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=fn; a.click(); URL.revokeObjectURL(a.href); updateStatus(`生成 ${fn}`); }
function toggleTopicPanel(){ const cont=document.getElementById('topicContainer'); if(cont.style.display==='none') cont.style.display='flex'; else cont.style.display='none'; saveData(); }
function collapseTopic(){ const cont=document.getElementById('topicContent'); if(cont.style.display==='none') cont.style.display='flex'; else cont.style.display='none'; }
function collapseFile(){ const list=document.getElementById('fileList'); const btn=document.getElementById('addFileSidebarBtn'); if(list.style.display==='none'){ list.style.display='block'; btn.style.display='block'; }else{ list.style.display='none'; btn.style.display='none'; } }
function collapseRun(){ const cont=document.getElementById(currentLanguage === 'cpp' ? 'cppRunContent' : 'htmlPreviewContainer'); if(cont && cont.style.display==='none') cont.style.display='flex'; else if(cont) cont.style.display='none'; }
function saveLayout() { let leftW = document.getElementById('leftPanel').offsetWidth; let topicFlex = document.getElementById('topicContainer').style.flex || '3'; let fileFlex = document.getElementById('filemanagerContainer').style.flex || '1'; let codeFlex = document.getElementById('codeContainer').style.flex || '3'; let runFlex = document.getElementById('runContainer').style.flex || '1'; localStorage.setItem('cpp_layout', JSON.stringify({ leftW, topicFlex, fileFlex, codeFlex, runFlex })); }
function loadLayout() { try{ let l=localStorage.getItem('cpp_layout'); if(l){ let d=JSON.parse(l); if(d.leftW) document.getElementById('leftPanel').style.width=d.leftW+'px'; if(d.topicFlex) document.getElementById('topicContainer').style.flex=d.topicFlex; if(d.fileFlex) document.getElementById('filemanagerContainer').style.flex=d.fileFlex; if(d.codeFlex) document.getElementById('codeContainer').style.flex=d.codeFlex; if(d.runFlex) document.getElementById('runContainer').style.flex=d.runFlex; } }catch(e){} }
function initResizeHandles() {
const leftRightHandle = document.getElementById('leftRightHandle'); let startX, startW; const onMove = (e)=>{ let newW=startW+(e.clientX-startX); if(newW>=180 && newW<=window.innerWidth*0.6) document.getElementById('leftPanel').style.width=newW+'px'; saveLayout(); }; const onUp=()=>{ document.removeEventListener('mousemove',onMove); document.removeEventListener('mouseup',onUp); }; leftRightHandle.addEventListener('mousedown',(e)=>{ startX=e.clientX; startW=document.getElementById('leftPanel').offsetWidth; document.addEventListener('mousemove',onMove); document.addEventListener('mouseup',onUp); e.preventDefault(); });
const leftVert = document.getElementById('leftVerticalHandle'); let startY, startTopicFlex, startFileFlex; const vertMove=(e)=>{ let deltaY=e.clientY-startY; let total=startTopicFlex+startFileFlex; let newTopic=Math.max(0.5, Math.min(total-0.5, startTopicFlex+deltaY/10)); let newFile=total-newTopic; document.getElementById('topicContainer').style.flex=newTopic; document.getElementById('filemanagerContainer').style.flex=newFile; saveLayout(); }; const vertUp=()=>{ document.removeEventListener('mousemove',vertMove); document.removeEventListener('mouseup',vertUp); }; leftVert.addEventListener('mousedown',(e)=>{ startY=e.clientY; startTopicFlex=parseFloat(getComputedStyle(document.getElementById('topicContainer')).flex)||3; startFileFlex=parseFloat(getComputedStyle(document.getElementById('filemanagerContainer')).flex)||1; document.addEventListener('mousemove',vertMove); document.addEventListener('mouseup',vertUp); e.preventDefault(); });
const rightVert = document.getElementById('rightVerticalHandle'); let startCodeFlex, startRunFlex; const rightMove=(e)=>{ let deltaY=e.clientY-startY; let total=startCodeFlex+startRunFlex; let newCode=Math.max(0.5, Math.min(total-0.5, startCodeFlex+deltaY/10)); let newRun=total-newCode; document.getElementById('codeContainer').style.flex=newCode; document.getElementById('runContainer').style.flex=newRun; saveLayout(); }; const rightUp=()=>{ document.removeEventListener('mousemove',rightMove); document.removeEventListener('mouseup',rightUp); }; rightVert.addEventListener('mousedown',(e)=>{ startY=e.clientY; startCodeFlex=parseFloat(getComputedStyle(document.getElementById('codeContainer')).flex)||3; startRunFlex=parseFloat(getComputedStyle(document.getElementById('runContainer')).flex)||1; document.addEventListener('mousemove',rightMove); document.addEventListener('mouseup',rightUp); e.preventDefault(); });
}
function toggleWebsites(open) { websitesOpen = open !== undefined ? open : !websitesOpen; const sidebar = document.getElementById('websitesSidebar'); const btn = document.getElementById('toggleWebsitesBtn'); if(websitesOpen) { sidebar.classList.add('open'); btn.textContent = '🌐 关闭网站'; btn.classList.add('active'); } else { sidebar.classList.remove('open'); btn.textContent = '🌐 常用网站'; btn.classList.remove('active'); } saveData(); }
function initTimeoutSlider() { const slider = document.getElementById('timeoutSlider'); const valueSpan = document.getElementById('timeoutValue'); slider.addEventListener('input', (e) => { compileTimeoutSec = parseFloat(e.target.value); valueSpan.innerText = compileTimeoutSec.toFixed(1) + " 秒"; saveData(); updateStatus(`编译超时已设为 ${compileTimeoutSec} 秒`); }); }
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function() {
editor = monaco.editor.create(document.getElementById('editor'), { value: defaultCpp, language: 'cpp', theme: 'vs', fontSize: editorFontSize, automaticLayout: true, minimap: { enabled: false }, tabSize: 4 });
monacoReady = true; loadData(); loadLayout();
editor.onDidChangeModelContent(() => { if(activeFileId){ const f=projects.find(f=>f.id===activeFileId); if(f){ f.content=editor.getValue(); saveData(); if(currentLanguage === 'html') refreshHtmlPreview(); } } });
applyTheme(); initResizeHandles(); initTimeoutSlider();
});
document.getElementById('fileMenuBtn').onclick = () => document.getElementById('fileDropdown').classList.toggle('show');
document.getElementById('editMenuBtn').onclick = () => document.getElementById('editDropdown').classList.toggle('show');
document.getElementById('newProjectItem').onclick = () => { newProj(); document.getElementById('fileDropdown').classList.remove('show'); };
document.getElementById('newFileItem').onclick = () => { addNew(); document.getElementById('fileDropdown').classList.remove('show'); };
document.getElementById('importFileItem').onclick = () => { document.getElementById('importFile').click(); document.getElementById('fileDropdown').classList.remove('show'); };
document.getElementById('exportFileItem').onclick = () => { exportCur(); document.getElementById('fileDropdown').classList.remove('show'); };
document.getElementById('clearOutputItem').onclick = () => { clearOut(); document.getElementById('fileDropdown').classList.remove('show'); };
document.getElementById('formatCodeItem').onclick = () => { formatCode(); document.getElementById('editDropdown').classList.remove('show'); };
document.getElementById('removeCommentsItem').onclick = () => { removeCommentsFunc(); document.getElementById('editDropdown').classList.remove('show'); };
document.getElementById('removeBlankLinesItem').onclick = () => { removeBlank(); document.getElementById('editDropdown').classList.remove('show'); };
document.getElementById('zoomInBtn').onclick = () => setFont(1); document.getElementById('zoomOutBtn').onclick = () => setFont(-1); document.getElementById('resetZoomBtn').onclick = resetFont;
document.getElementById('themeToggleBtn').onclick = toggleTheme; document.getElementById('chineseErrorToggleBtn').onclick = toggleChineseError;
document.getElementById('runBtn').onclick = compileRun; document.getElementById('generateCppBtn').onclick = genCpp;
document.getElementById('addFileSidebarBtn').onclick = addNew; document.getElementById('cppStdSelect').onchange = e => { currentCppStd = e.target.value; saveData(); };
document.getElementById('showTopicBtn').onclick = toggleTopicPanel; document.getElementById('collapseTopicBtn').onclick = collapseTopic; document.getElementById('collapseFileBtn').onclick = collapseFile; document.getElementById('collapseRunBtn').onclick = collapseRun;
document.getElementById('topicFontPlus').onclick = () => { topicFontSize = Math.min(28, topicFontSize+2); document.getElementById('topicTextarea').style.fontSize = topicFontSize+'px'; saveData(); };
document.getElementById('topicFontMinus').onclick = () => { topicFontSize = Math.max(10, topicFontSize-2); document.getElementById('topicTextarea').style.fontSize = topicFontSize+'px'; saveData(); };
document.getElementById('toggleWebsitesBtn').onclick = () => toggleWebsites(); document.getElementById('closeWebsitesBtn').onclick = () => toggleWebsites(false);
document.getElementById('langToggleBtn').onclick = toggleLanguage;
document.getElementById('refreshPreviewBtn').onclick = () => refreshHtmlPreview();
document.getElementById('importFile').addEventListener('change', e => { if(e.target.files[0]) importFile(e.target.files[0]); e.target.value=''; });
document.getElementById('minimizeHintBtn').onclick = () => updateStatus("独立应用模式"); document.getElementById('closeHintBtn').onclick = () => updateStatus("关闭标签页退出");
window.addEventListener('beforeunload', () => saveData());
document.getElementById('topicContainer').style.display = 'none';
</script>
</body>
</html>