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

View File

@@ -378,6 +378,23 @@
color: #f44336; 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抽屉标签样式 */ /* API抽屉标签样式 */
.apiDrawerTab { .apiDrawerTab {
position: fixed; position: fixed;
@@ -486,19 +503,15 @@
} }
} }
/* API抽屉样式 */ /* API面板样式 */
.apiDrawer { .apiDrawer {
position: fixed; position: relative;
top: 0; flex-shrink: 0;
right: -20vw;
/* 初始隐藏在右侧 */
width: 20vw; width: 20vw;
height: 100vh; height: 100%;
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%); background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
border-left: 2px solid #444; border-right: 2px solid #444;
box-shadow: -5px 0 20px rgba(0, 0, 0, 0.3); box-shadow: 2px 0 12px rgba(0, 0, 0, 0.2);
z-index: 1000;
transition: right 0.3s ease;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -46,7 +46,7 @@
<img class="level-card-cover" src="images/level1_cover.png" alt="" /> <img class="level-card-cover" src="images/level1_cover.png" alt="" />
<div class="level-card-text"> <div class="level-card-text">
<!-- <div class="lang-name">图形化</div> --> <!-- <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-sub">编程基础,循环入门</div>
<div class="level-card-desc"></div> <div class="level-card-desc"></div>
</div> </div>

View File

@@ -106,12 +106,12 @@ document.addEventListener('DOMContentLoaded', function () {
// Update Level Card Text // Update Level Card Text
const levelInfo = { const levelInfo = {
1: { title: "Level A <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: "变量入门,循环进阶,变量进阶" }, 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: "变量进阶,单条件分支入门,双条件分支入门,复杂路线规划" }, 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: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用" }, 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: "循环嵌套进阶,无参数函数入门,单参数函数入门" }, 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: "综合运用与项目实战" } 6: { title: "Level F <span style='font-size: 0.7em'>数智时代</span>", sub: "综合运用与项目实战", cover: 3 }
}; };
const lv = index + 1; const lv = index + 1;
const titleEl = document.querySelector('.level-card-head'); const titleEl = document.querySelector('.level-card-head');
@@ -124,7 +124,8 @@ document.addEventListener('DOMContentLoaded', function () {
// Update Cover Image // Update Cover Image
const coverEl = document.querySelector('.level-card-cover'); const coverEl = document.querySelector('.level-card-cover');
if (coverEl) { 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(); gamelvid = (index + 1).toString();

View File

@@ -126,7 +126,7 @@ self.onmessage = async (event) => {
const { type, code, unity_data, requestId} = event.data; const { type, code, unity_data, requestId} = event.data;
if (type === "response") { if (type === "response") {
self.unitydata = unity_data self.unitydata = unity_data
console.log("get new response", self.unitydata.externalDatas[0].position) console.log("get new response", self.unitydata && self.unitydata.externalDatas && self.unitydata.externalDatas[0] && self.unitydata.externalDatas[0].position)
const pending = pendingRequests.get(requestId); const pending = pendingRequests.get(requestId);
if (pending) { if (pending) {
pending.resolve(); pending.resolve();
@@ -152,7 +152,7 @@ self.onmessage = async (event) => {
// 发送执行完成的消息 // 发送执行完成的消息
self.postMessage({ type: "executionComplete" }); self.postMessage({ type: "executionComplete" });
console.log("Python 执行完成",window.isPythonRun); console.log("Python 执行完成");
self.postMessage({ type: "result", result }); self.postMessage({ type: "result", result });
} catch (error) { } catch (error) {
self.postMessage({ type: "error", error: error.message }); self.postMessage({ type: "error", error: error.message });

View File

@@ -19,7 +19,6 @@
</assembly> </assembly>
<assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all"> <assembly fullname="Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" preserve="all">
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" /> <type fullname="UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.AtlasSpriteProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" /> <type fullname="UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" /> <type fullname="UnityEngine.ResourceManagement.ResourceProviders.InstanceProvider" preserve="all" />
<type fullname="UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" preserve="all" /> <type fullname="UnityEngine.ResourceManagement.ResourceProviders.LegacyResourcesProvider" preserve="all" />

Some files were not shown because too many files have changed in this diff Show More