feat:部署前配置

This commit is contained in:
Evan
2026-09-10 15:15:39 +08:00
parent 19bb63cd87
commit 8dfd42397f
421 changed files with 194 additions and 371 deletions

View File

@@ -32,8 +32,8 @@ const StageWrapperCppClang = () => {
const [wasmModule, setWasmModule] = useState(null);
const [syntaxErrorModalVisible, setSyntaxErrorModalVisible] = useState(false);
const [syntaxErrors, setSyntaxErrors] = useState([]);
const [apiDrawerOpen, setApiDrawerOpen] = useState(false);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const [apiSelectedCategory, setApiSelectedCategory] = useState('player');
const [isApiPanelReady, setIsApiPanelReady] = useState(() => window.iscreate === 1);
const [overlayActive, setOverlayActive] = useState(false);
const MAX_CODE_LINES = 500;
@@ -41,19 +41,24 @@ const StageWrapperCppClang = () => {
const editorRef = useRef(null);
const lastValidCppCodeRef = useRef(cppCode);
// 计算CodeMirror宽度的函数
const getCodeMirrorWidth = () => {
if (!apiDrawerOpen) return '54vw';
// 根据屏幕宽度调整
if (windowWidth <= 900) {
return '19vw'; // 54vw - 35vw = 19vw
} else if (windowWidth <= 1200) {
return '29vw'; // 54vw - 25vw = 29vw
} else {
return '34vw'; // 54vw - 20vw = 34vw
}
};
// 游戏引擎加载完成前API 面板显示加载态并屏蔽点击5 秒兜底自动解除)
useEffect(() => {
if (isApiPanelReady) return undefined;
const timer = setInterval(() => {
if (window.iscreate === 1) {
setIsApiPanelReady(true);
clearInterval(timer);
}
}, 300);
const fallback = setTimeout(() => {
setIsApiPanelReady(true);
clearInterval(timer);
}, 5000);
return () => {
clearInterval(timer);
clearTimeout(fallback);
};
}, [isApiPanelReady]);
// API参考数据
const apiReference = {
@@ -238,17 +243,6 @@ const StageWrapperCppClang = () => {
useEffect(() => {
initializeEmscripten();
// 添加窗口大小变化监听器
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// 初始化基于Emscripten的C++执行环境
@@ -1118,27 +1112,18 @@ const StageWrapperCppClang = () => {
);
};
// API参考抽屉组件
// API参考面板(与 Python 一样常驻在编辑区左侧)
const ApiDrawer = () => {
const [selectedCategory, setSelectedCategory] = useState('syntax');
const selectedCategory = apiSelectedCategory;
const setSelectedCategory = setApiSelectedCategory;
return (
<div className={`${styles.apiDrawer} ${apiDrawerOpen ? styles.apiDrawerOpen : ''}`}>
{/* 抽屉头部 */}
<div className={styles.apiDrawer}>
<div className={styles.apiDrawerHeader}>
<h3>🔧 API参考</h3>
<button
className={styles.apiDrawerClose}
onClick={() => setApiDrawerOpen(false)}
title="关闭API参考"
>
×
</button>
<h3>🔧 C++ API参考</h3>
</div>
{/* 抽屉内容 */}
<div className={styles.apiDrawerContent}>
{/* 分类导航 */}
<div className={styles.apiDrawerCategories}>
{Object.entries(apiReference).map(([key, category]) => (
<button
@@ -1152,7 +1137,6 @@ const StageWrapperCppClang = () => {
))}
</div>
{/* API列表 */}
<div className={styles.apiDrawerList}>
{apiReference[selectedCategory]?.apis.map((api, index) => (
<div key={index} className={styles.apiDrawerItem}>
@@ -1176,6 +1160,12 @@ const StageWrapperCppClang = () => {
))}
</div>
</div>
{!isApiPanelReady && (
<div className={styles.apiDrawerLoading}>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>游戏引擎加载中完成后即可插入代码</div>
</div>
)}
</div>
);
};
@@ -1209,10 +1199,6 @@ const StageWrapperCppClang = () => {
}
});
// 关闭API参考窗口
setApiDrawerOpen(false);
// 聚焦编辑器
view.focus();
}
}
@@ -1223,166 +1209,137 @@ const StageWrapperCppClang = () => {
<ErrorModal />
<CompilationModal />
<SyntaxErrorModal />
<div onWheel={(e) => e.stopPropagation()} style={{
height: '100vh',
width: '100%',
position: 'relative',
overflow: 'hidden'
}}>
<div style={{
position: 'relative',
height: '100%',
width: '100%'
}}>
{/* API抽屉标签 */}
<div
className={`${styles.apiDrawerTab} ${apiDrawerOpen ? styles.apiDrawerTabOpen : ''}`}
onClick={() => setApiDrawerOpen(!apiDrawerOpen)}
title={apiDrawerOpen ? "关闭API参考" : "打开API参考"}
>
<div className={styles.apiDrawerTabIcon}>
{apiDrawerOpen ? '▶' : '◀'}
</div>
<div className={styles.apiDrawerTabText}>
{apiDrawerOpen ? '关闭' : 'API'}
</div>
</div>
{/* API抽屉 */}
<ApiDrawer />
{/* CodeMirror编辑器 */}
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
<CodeMirror
ref={editorRef}
<div className={styles.editorWithApi}>
<ApiDrawer />
<div
className={styles.editorPane}
onWheel={(e) => e.stopPropagation()}
>
<CodeMirror
ref={editorRef}
style={{
width: '100%',
height: '100%',
position: 'relative',
backgroundColor: '#010101',
overflow: 'auto',
fontSize: '20px'
}}
value={cppCode}
theme={abcdef}
extensions={[
cpp(),
createCppCompletions(),
keymap.of(completionKeymap),
keymap.of(snippetKeymap),
classname({
add: (lineNumber) => {
if (lineNumber === highlightedLine) {
return 'highlighted-line';
}
return null;
}
}),
EditorView.theme({
'&': {
height: '100%',
width: '100%'
},
'.cm-editor': {
height: '100%',
width: '100%'
},
'.cm-scroller': {
height: '100%',
overflow: 'auto'
},
'.cm-content': {
minHeight: '100%',
padding: '10px'
},
'.highlighted-line': {
backgroundColor: '#ffeb3b80',
borderLeft: '4px solid #ffeb3b',
borderRight: '2px solid #ffeb3b',
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
animation: 'pulse 1s ease-in-out'
},
'@keyframes pulse': {
'0%': { backgroundColor: '#ffeb3b80' },
'50%': { backgroundColor: '#ffeb3ba0' },
'100%': { backgroundColor: '#ffeb3b80' }
},
fontSize: "20px"
})
]}
basicSetup={{
lineNumbers: true,
highlightActiveLine: true,
highlightSelectionMatches: true,
searchKeymap: true,
autocompletion: true
}}
onChange={(value) => {
const lines = value.split(/\r\n|\r|\n/);
if (lines.length > MAX_CODE_LINES) {
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
setErrorModalVisible(true);
if (editorRef.current && editorRef.current.view) {
const view = editorRef.current.view;
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: lastValidCppCodeRef.current
}
});
}
return;
}
lastValidCppCodeRef.current = value;
setCppCode(value);
updateBlockCount(value);
}}
/>
{overlayActive && (
<div
style={{
width: getCodeMirrorWidth(),
height: '100%',
position: 'relative',
position: 'absolute',
top: 0,
left: 0,
backgroundColor: '#010101',
fontSize: '20px',
transition: 'width 0.3s ease'
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.25)',
cursor: 'not-allowed',
zIndex: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none'
}}
value={cppCode}
theme={abcdef}
extensions={[
cpp(),
createCppCompletions(),
keymap.of(completionKeymap),
keymap.of(snippetKeymap),
classname({
add: (lineNumber) => {
if (lineNumber === highlightedLine) {
return 'highlighted-line';
}
return null;
}
}),
EditorView.theme({
'&': {
height: '100%',
width: '100%'
},
'.cm-editor': {
height: '100%',
width: '100%'
},
'.cm-scroller': {
height: '100%',
overflow: 'auto'
},
'.cm-content': {
minHeight: '100%',
padding: '10px'
},
'.highlighted-line': {
backgroundColor: '#ffeb3b80',
borderLeft: '4px solid #ffeb3b',
borderRight: '2px solid #ffeb3b',
boxShadow: '0 0 10px rgba(255, 235, 59, 0.3)',
animation: 'pulse 1s ease-in-out'
},
'@keyframes pulse': {
'0%': { backgroundColor: '#ffeb3b80' },
'50%': { backgroundColor: '#ffeb3ba0' },
'100%': { backgroundColor: '#ffeb3b80' }
},
fontSize: "20px"
})
]}
basicSetup={{
lineNumbers: true,
highlightActiveLine: true,
highlightSelectionMatches: true,
searchKeymap: true,
autocompletion: true
}}
onChange={(value) => {
const lines = value.split(/\r\n|\r|\n/);
if (lines.length > MAX_CODE_LINES) {
setErrorMessage(`代码不能超过${MAX_CODE_LINES}行!当前已有${lines.length}行,请删除多余代码后再编辑。`);
setErrorModalVisible(true);
// 使用 CodeMirror dispatch 直接回滚编辑器内容
if (editorRef.current && editorRef.current.view) {
const view = editorRef.current.view;
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: lastValidCppCodeRef.current
}
});
}
return;
}
lastValidCppCodeRef.current = value;
setCppCode(value);
// 计算代码行数并更新window.blockcount
updateBlockCount(value);
}}
/>
{overlayActive && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.25)',
cursor: 'not-allowed',
zIndex: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
userSelect: 'none'
}}
onWheel={(e) => e.preventDefault()}
onTouchMove={(e) => e.preventDefault()}
onClick={(e) => e.preventDefault()}
onMouseDown={(e) => e.preventDefault()}
>
<div style={{
color: '#fff',
fontSize: '28px',
fontWeight: 'bold',
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
letterSpacing: '4px',
animation: 'blink 1.5s ease-in-out infinite'
}}>
执行中...
</div>
<style>{`
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
`}</style>
onWheel={(e) => e.preventDefault()}
onTouchMove={(e) => e.preventDefault()}
onClick={(e) => e.preventDefault()}
onMouseDown={(e) => e.preventDefault()}
>
<div style={{
color: '#fff',
fontSize: '28px',
fontWeight: 'bold',
textShadow: '0 2px 8px rgba(0,0,0,0.6)',
letterSpacing: '4px',
animation: 'blink 1.5s ease-in-out infinite'
}}>
执行中...
</div>
)}
</div>
<style>{`
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
`}</style>
</div>
)}
</div>
</div>
</>

View File

@@ -378,6 +378,23 @@
color: #f44336;
}
/* API参考 + 编辑器并排:参考在左,编辑器靠右(与 Python 一致) */
.editorWithApi {
display: flex;
flex-direction: row;
height: 100%;
width: 54vw;
overflow: hidden;
}
.editorPane {
flex: 1;
min-width: 0;
height: 100%;
position: relative;
overflow: auto;
}
/* API抽屉标签样式 */
.apiDrawerTab {
position: fixed;
@@ -486,19 +503,15 @@
}
}
/* API抽屉样式 */
/* API面板样式 */
.apiDrawer {
position: fixed;
top: 0;
right: -20vw;
/* 初始隐藏在右侧 */
position: relative;
flex-shrink: 0;
width: 20vw;
height: 100vh;
height: 100%;
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
border-left: 2px solid #444;
box-shadow: -5px 0 20px rgba(0, 0, 0, 0.3);
z-index: 1000;
transition: right 0.3s ease;
border-right: 2px solid #444;
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.2);
overflow: hidden;
display: flex;
flex-direction: column;

View File

@@ -46,7 +46,7 @@
<img class="level-card-cover" src="images/level1_cover.png" alt="" />
<div class="level-card-text">
<!-- <div class="lang-name">图形化</div> -->
<div class="level-card-head">Level A</div><span class="lang-name">(图形化)</span>
<div class="level-card-head">Level A <span style='font-size: 0.7em'>丝路青年</span></div><span class="lang-name">(图形化)</span>
<div class="level-card-sub">编程基础,循环入门</div>
<div class="level-card-desc"></div>
</div>

View File

@@ -106,12 +106,12 @@ document.addEventListener('DOMContentLoaded', function () {
// Update Level Card Text
const levelInfo = {
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门" },
2: { title: "Level B <span style='font-size: 0.7em'>红色之旅</span>", sub: "变量入门,循环进阶,变量进阶" },
3: { title: "Level C <span style='font-size: 0.7em'>数智时代</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划" },
4: { title: "Level D <span style='font-size: 0.7em'>国宝秘境</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用" },
5: { title: "Level E <span style='font-size: 0.7em'>雪域征途</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门" },
6: { title: "Level F <span style='font-size: 0.7em'>红色之旅</span>", sub: "综合运用与项目实战" }
1: { title: "Level A <span style='font-size: 0.7em'>丝路青年</span>", sub: "编程基础,循环入门", cover: 1 },
2: { title: "Level B <span style='font-size: 0.7em'>丝路青年</span>", sub: "变量入门,循环进阶,变量进阶", cover: 1 },
3: { title: "Level C <span style='font-size: 0.7em'>红色之旅</span>", sub: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划", cover: 2 },
4: { title: "Level D <span style='font-size: 0.7em'>红色之旅</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用", cover: 2 },
5: { title: "Level E <span style='font-size: 0.7em'>数智时代</span>", sub: "循环嵌套进阶,无参数函数入门,单参数函数入门", cover: 3 },
6: { title: "Level F <span style='font-size: 0.7em'>数智时代</span>", sub: "综合运用与项目实战", cover: 3 }
};
const lv = index + 1;
const titleEl = document.querySelector('.level-card-head');
@@ -124,7 +124,8 @@ document.addEventListener('DOMContentLoaded', function () {
// Update Cover Image
const coverEl = document.querySelector('.level-card-cover');
if (coverEl) {
coverEl.src = `images/level${lv}_cover.png`;
const coverId = (levelInfo[lv] && levelInfo[lv].cover) || lv;
coverEl.src = `images/level${coverId}_cover.png`;
}
gamelvid = (index + 1).toString();