Compare commits

...

14 Commits

Author SHA1 Message Date
Evan
bb78d8bf5c feat:更改配置项 2026-09-20 13:59:10 +08:00
Evan
c4641aee19 批量修改 2026-09-17 17:04:15 +08:00
Evan
782f76170f c++bug修复 2026-09-17 11:53:14 +08:00
Evan
d83654837a c++优化 2026-09-15 17:55:13 +08:00
Evan
899adbc9d4 feat: ai功能优化 2026-09-15 17:55:05 +08:00
Evan
4499d7fdef no message 2026-09-14 17:52:02 +08:00
Evan
11d7496b54 feat:更新cocos包 2026-09-11 16:06:20 +08:00
Evan
1012af6039 feat:修改国宝chainese 2026-09-11 16:05:58 +08:00
Evan
37e0413576 feat: 更新大版本 2026-09-11 09:32:07 +08:00
Evan
8dfd42397f feat:部署前配置 2026-09-10 15:15:39 +08:00
Evan
19bb63cd87 整理config 2026-09-07 11:42:49 +08:00
Evan
bae333ba7c Merge branch 'cocos-main'
# Conflicts:
#	docker-conf/conf.d/nginx.conf
#	nginx.conf
#	scratch-blocks/build.py
#	scratch-blocks/cleanup.sh
#	scratch-blocks/i18n/create_messages.py
#	scratch-blocks/i18n/dedup_json.py
#	scratch-blocks/i18n/js_to_json.py
#	scratch-blocks/i18n/json_to_js.py
#	scratch-blocks/i18n/sync_tx_translations.js
#	scratch-blocks/i18n/xliff_to_json.py
#	scratch-blocks/local_build.sh
#	scratch-blocks/media/icons/control_forever.svg
#	scratch-blocks/media/icons/control_repeat.svg
#	scratch-blocks/media/icons/control_wait.svg
#	scratch-blocks/media/icons/event_broadcast_blue.svg
#	scratch-blocks/media/icons/event_broadcast_coral.svg
#	scratch-blocks/media/icons/event_broadcast_green.svg
#	scratch-blocks/media/icons/event_broadcast_magenta.svg
#	scratch-blocks/media/icons/event_broadcast_orange.svg
#	scratch-blocks/media/icons/event_broadcast_purple.svg
#	scratch-blocks/media/icons/event_when-broadcast-received_blue.svg
#	scratch-blocks/media/icons/event_when-broadcast-received_coral.svg
#	scratch-blocks/media/icons/event_when-broadcast-received_green.svg
#	scratch-blocks/media/icons/event_when-broadcast-received_magenta.svg
#	scratch-blocks/media/icons/event_when-broadcast-received_orange.svg
#	scratch-blocks/media/icons/event_when-broadcast-received_purple.svg
#	scratch-blocks/media/icons/event_whenflagclicked.svg
#	scratch-blocks/media/icons/set-led_blue.svg
#	scratch-blocks/media/icons/set-led_coral.svg
#	scratch-blocks/media/icons/set-led_green.svg
#	scratch-blocks/media/icons/set-led_magenta.svg
#	scratch-blocks/media/icons/set-led_mystery.svg
#	scratch-blocks/media/icons/set-led_orange.svg
#	scratch-blocks/media/icons/set-led_purple.svg
#	scratch-blocks/media/icons/set-led_white.svg
#	scratch-blocks/media/icons/set-led_yellow.svg
#	scratch-blocks/media/icons/wedo_motor-clockwise.svg
#	scratch-blocks/media/icons/wedo_motor-counterclockwise.svg
#	scratch-blocks/media/icons/wedo_when-distance_close.svg
#	scratch-blocks/media/icons/wedo_when-tilt-backward.svg
#	scratch-blocks/media/icons/wedo_when-tilt-forward.svg
#	scratch-blocks/media/icons/wedo_when-tilt-left.svg
#	scratch-blocks/media/icons/wedo_when-tilt-right.svg
#	scratch-blocks/media/icons/wedo_when-tilt.svg
#	scratch-blocks/package-lock.json
#	scratch-blocks/pull_from_blockly.sh
#	scratch-gui/competitio_login/index.html
#	scratch-gui/package-lock.json
#	scratch-gui/package.json
#	scratch-gui/prune-gh-pages.sh
#	scratch-gui/src/components/menu-bar/menu-bar.css
#	scratch-gui/src/components/menu-bar/menu-bar.jsx
#	scratch-gui/src/components/nopermission-modal/NoPermissionModal.jsx
#	scratch-gui/src/components/stage-cpp-unity/currentLevelChoose.jsx
#	scratch-gui/src/components/stage-cpp-unity/stage-unity.jsx
#	scratch-gui/src/components/stage-cpp/cpp-parser.js
#	scratch-gui/src/components/stage-cpp/stage-cpp-clang.jsx
#	scratch-gui/src/components/stage-cpp/stage-cpp.css
#	scratch-gui/src/components/stage-python-unity/currentLevelChoose.jsx
#	scratch-gui/src/components/stage-python-unity/stage-unity.jsx
#	scratch-gui/src/components/stage-python/stage-python.css
#	scratch-gui/src/components/stage-python/stage-python.jsx
#	scratch-gui/src/components/stage-unity/currentLevelChoose.jsx
#	scratch-gui/src/components/stage-unity/stage-unity.jsx
#	scratch-gui/src/lib/libraries/decks/steps/add-effects.es.png
#	scratch-gui/src/lib/libraries/decks/steps/animate-char-add-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/animate-char-change-color.es.png
#	scratch-gui/src/lib/libraries/decks/steps/animate-char-jump.es.png
#	scratch-gui/src/lib/libraries/decks/steps/animate-char-move.es.png
#	scratch-gui/src/lib/libraries/decks/steps/animate-char-say-something.es.png
#	scratch-gui/src/lib/libraries/decks/steps/animate-char-talk.es.png
#	scratch-gui/src/lib/libraries/decks/steps/change-size.es.png
#	scratch-gui/src/lib/libraries/decks/steps/chase-game-change-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/chase-game-move-randomly.es.png
#	scratch-gui/src/lib/libraries/decks/steps/chase-game-play-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/chase-game-right-left.es.png
#	scratch-gui/src/lib/libraries/decks/steps/chase-game-up-down.es.png
#	scratch-gui/src/lib/libraries/decks/steps/cn-backdrop.es.png
#	scratch-gui/src/lib/libraries/decks/steps/cn-collect.es.png
#	scratch-gui/src/lib/libraries/decks/steps/cn-glide.es.png
#	scratch-gui/src/lib/libraries/decks/steps/cn-say.es.png
#	scratch-gui/src/lib/libraries/decks/steps/cn-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-01-say-something.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-02-animate.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-03-select-different-character.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-04-use-minus-sign.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-05-grow-shrink.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-06-select-another-different-character.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-07-jump.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-08-change-scenes.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-09-glide-around.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-10-change-costumes.es.png
#	scratch-gui/src/lib/libraries/decks/steps/code-cartoon-11-choose-more-characters.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-choose-character.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-flying-heart.es.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-keep-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-make-interactive.es.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-move-scenery.es.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-object-to-collect.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-say-something.es.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-select-flyer.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/fly-switch-costume.es.png
#	scratch-gui/src/lib/libraries/decks/steps/glide-around-back-and-forth.es.png
#	scratch-gui/src/lib/libraries/decks/steps/glide-around-point.es.png
#	scratch-gui/src/lib/libraries/decks/steps/hide-show.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-change-costumes.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-choose-another-backdrop.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-choose-another-sprite.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-choose-any-sprite.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-choose-backdrop.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-choose-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-click-green-flag.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-fly-around.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-glide-to-point.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-grow-shrink.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-left-right.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-type-what-you-want.es.png
#	scratch-gui/src/lib/libraries/decks/steps/imagine-up-down.es.png
#	scratch-gui/src/lib/libraries/decks/steps/move-arrow-keys-left-right.es.png
#	scratch-gui/src/lib/libraries/decks/steps/move-arrow-keys-up-down.es.png
#	scratch-gui/src/lib/libraries/decks/steps/music-make-beat.es.png
#	scratch-gui/src/lib/libraries/decks/steps/music-make-beatbox.es.png
#	scratch-gui/src/lib/libraries/decks/steps/music-make-song.es.png
#	scratch-gui/src/lib/libraries/decks/steps/music-play-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/name-change-color.es.png
#	scratch-gui/src/lib/libraries/decks/steps/name-grow.es.png
#	scratch-gui/src/lib/libraries/decks/steps/name-play-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/name-spin.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-add-backdrop.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-add-ball-sprite.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-add-code-to-ball.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-bounce-around.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-choose-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-game-over.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-insert-change-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-move-the-paddle.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-reset-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pong-select-ball.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/pop-game-change-color.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pop-game-change-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pop-game-play-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pop-game-random-position.es.png
#	scratch-gui/src/lib/libraries/decks/steps/pop-game-reset-score.es.png
#	scratch-gui/src/lib/libraries/decks/steps/record-a-sound-choose-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/record-a-sound-click-record.es.png
#	scratch-gui/src/lib/libraries/decks/steps/record-a-sound-play-your-sound.es.png
#	scratch-gui/src/lib/libraries/decks/steps/record-a-sound-press-record-button.es.png
#	scratch-gui/src/lib/libraries/decks/steps/record-a-sound-sounds-tab.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-change-color.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-grow-shrink.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-move-around.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-say-something.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-set-voice.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-song.es.png
#	scratch-gui/src/lib/libraries/decks/steps/speech-spin.es.png
#	scratch-gui/src/lib/libraries/decks/steps/spin-point-in-direction.es.png
#	scratch-gui/src/lib/libraries/decks/steps/spin-turn.es.png
#	scratch-gui/src/lib/libraries/decks/steps/story-conversation.es.png
#	scratch-gui/src/lib/libraries/decks/steps/story-hide-character.es.png
#	scratch-gui/src/lib/libraries/decks/steps/story-say-something.es.png
#	scratch-gui/src/lib/libraries/decks/steps/story-show-character.es.png
#	scratch-gui/src/lib/libraries/decks/steps/story-switch-backdrop.es.png
#	scratch-gui/src/lib/libraries/decks/steps/switch-costumes.es.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-10-choose-third-backdrop.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-12-dance-moves.es.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-2-choose-sprite.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-4-choose-backdrop.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-6-choose-another-sprite.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-7-move-around.es.png
#	scratch-gui/src/lib/libraries/decks/steps/talking-8-choose-another-backdrop.LTR.png
#	scratch-gui/src/lib/libraries/decks/steps/video-animate.es.png
#	scratch-gui/src/lib/libraries/decks/steps/video-pet.es.png
#	scratch-gui/src/lib/libraries/decks/steps/video-pop.es.png
#	scratch-gui/src/playground/aboutus.ejs
#	scratch-gui/src/playground/checkStatus.js
#	scratch-gui/src/playground/competition.ejs
#	scratch-gui/src/playground/competition.js
#	scratch-gui/src/playground/competition_list.ejs
#	scratch-gui/src/playground/competition_list.js
#	scratch-gui/src/playground/competition_sum.ejs
#	scratch-gui/src/playground/competition_sum.js
#	scratch-gui/src/playground/config.js
#	scratch-gui/src/playground/home.ejs
#	scratch-gui/src/playground/import-check.js
#	scratch-gui/src/playground/leaderboard.ejs
#	scratch-gui/src/playground/login-new.ejs
#	scratch-gui/src/playground/login.ejs
#	scratch-gui/src/playground/login.js
#	scratch-gui/src/playground/matchsum.js
#	scratch-gui/src/playground/programming-learning.ejs
#	scratch-gui/src/playground/programming-learning.js
#	scratch-gui/src/playground/signup-new.ejs
#	scratch-gui/src/playground/third-part-login.js
#	scratch-gui/src/playground/welcome.ejs
#	scratch-gui/src/playground/welcome.js
#	scratch-gui/static/css/welcome-new.css
#	scratch-gui/static/pyodide/pyodide.asm.wasm.br
#	scratch-gui/static/unity/Build/mstest5.data.br
#	scratch-gui/static/unity/Build/mstest5.framework.js.br
#	scratch-gui/static/unity/Build/mstest5.loader.js
#	scratch-gui/static/unity/Build/mstest5.wasm.br
#	scratch-gui/static/unity/StreamingAssets/aa/AddressablesLink/link.xml
#	scratch-gui/static/unity/StreamingAssets/aa/catalog.json
#	scratch-gui/static/unity/StreamingAssets/aa/settings.json
#	scratch-gui/webpack.config.js
#	scratch-l10n/scripts/build-data.js
#	scratch-l10n/scripts/build-i18n-src.js
#	scratch-l10n/scripts/tw-push.js
#	scratch-l10n/scripts/tx-pull-help-articles.js
#	scratch-l10n/scripts/tx-pull-help-names.js
#	scratch-l10n/scripts/tx-pull-locale-articles.js
#	scratch-l10n/scripts/tx-pull-www.js
#	scratch-l10n/scripts/tx-push-help.js
#	scratch-l10n/scripts/tx-push-src.js
#	scratch-l10n/scripts/update-translations.sh
#	scratch-l10n/scripts/validate-www.js
#	scratch-vm/package-lock.json
#	scratch-vm/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3
#	scratch-vm/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3
#	scratch-vm/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3
#	scratch-vm/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3
#	scratch-vm/test/fixtures/sprite.json
2026-09-07 11:35:22 +08:00
Evan
badbc11154 init 2026-09-07 09:55:36 +08:00
Evan
9b6892f2ed init cocos-main 2026-09-07 09:20:58 +08:00
596 changed files with 22857 additions and 2151 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
docker-conf/.DS_Store vendored Normal file

Binary file not shown.

BIN
docker-conf/conf.d/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -17,28 +17,43 @@ server {
return 301 /competition.html?competition_id=13;
}
# Brotli 压缩 WebAssembly 文件处理
# Unity 预压缩文件:禁止再次 gzip避免 Content-Length 与实际内容不一致
location ~* \.wasm\.br$ {
default_type "";
gzip off;
default_type application/wasm;
add_header Content-Encoding br;
add_header Content-Type application/wasm always;
add_header Vary Accept-Encoding;
expires 30d;
add_header Accept-Ranges bytes;
expires 1h;
}
location ~* \.asm\.js\.br$ {
default_type "";
location ~* \.js\.br$ {
gzip off;
default_type application/javascript;
add_header Content-Encoding br;
add_header Content-Type application/javascript always;
add_header Vary Accept-Encoding;
expires 30d;
add_header Accept-Ranges bytes;
expires 1h;
}
location ~* \.data\.br$ {
gzip off;
default_type application/octet-stream;
add_header Content-Encoding br;
add_header Vary Accept-Encoding;
add_header Accept-Ranges bytes;
expires 1h;
}
# 通用 Brotli 文件处理
location ~* \.br$ {
gzip off;
add_header Content-Encoding br;
add_header Vary Accept-Encoding;
expires 30d;
add_header Accept-Ranges bytes;
expires 1h;
}
# 静态资源缓存配置

View File

@@ -37,9 +37,12 @@ server {
# 通用 Brotli 文件处理
location ~* \.br$ {
gzip off;
gzip_static off;
add_header Content-Encoding br;
add_header Vary Accept-Encoding;
expires 30d;
add_header Accept-Ranges bytes;
expires 1h;
}
# 静态资源缓存配置

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -383,8 +383,8 @@
<footer>
<div class="footer-line" role="presentation"></div>
<p class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">公网安备51011202000980</a>
<p class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="assets/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />公网安备31011502407417</a>
© 灵丌编程001CODE.COM</p>
</footer>
</div>

View File

@@ -28,6 +28,8 @@
"test:lint": "eslint . --ext .js,.jsx",
"test:unit": "jest test[\\\\/]unit[\\\\/]addons",
"test:smoke": "jest --runInBand test[\\\\/]smoke",
"test:cpp": "node scripts/test-cpp-regressions.cjs",
"test:devtool": "node scripts/test-devtool-control.cjs",
"watch": "webpack --colors --watch"
},
"config": {

View File

@@ -0,0 +1,71 @@
// Read-only diagnostic: exercise the current editor interpreter with mocked game APIs.
// Run from scratch-gui: node scripts/diagnose-cpp.cjs
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const babel = require('@babel/core');
const filename = path.resolve(__dirname, '../src/components/stage-cpp/cpp-parser.js');
const transformed = babel.transformSync(fs.readFileSync(filename, 'utf8'), {
filename, configFile: false, babelrc: false,
plugins: ['@babel/plugin-transform-modules-commonjs']
}).code;
const context = {
exports: {}, require, setTimeout,
console: {log() {}, warn() {}, error() {}}, window: {}
};
vm.runInNewContext(transformed, context, {filename});
const {CppCodeExecutor, validateCppSyntax} = context.exports;
const main = body => `int main() {\n${body}\n}`;
const customer = `void test() {
for (int i = 0; i < 2; i++) {
}
}
int main() {
turnLeft(-1);
for (int i = 0; i < 1; i++) {
test();
playerMove(i);
}
}`;
const cases = [
['customer_local_i', customer, {moves: [0]}],
['customer_renamed_inner_j', customer.replace('int i = 0; i < 2; i++', 'int j = 0; j < 2; j++'), {moves: [0]}],
['cout_literal', main('cout << "Hello" << endl;'), {output: ['Hello\n']}],
['cout_variable', main('int i = 7;\ncout << "i=" << i << endl;'), {output: ['i=7\n']}],
['cout_string_assignment_shape', main('cout << "value=42" << endl;'), {output: ['value=42\n']}],
['cout_increment_expression', main('int i = 0;\ncout << i++ << endl;\nplayerMove(i);'), {output: ['0\n'], moves: [1]}],
['cout_increment_literal', main('cout << "C++" << endl;'), {output: ['C++\n']}],
['cout_std_qualified', main('std::cout << "Hello" << std::endl;'), {output: ['Hello\n']}],
['main_return_zero', main('return 0;'), {error: null}],
['legal_space_before_call', main('playerMove (1);'), {valid: true, moves: [1]}],
['nested_loop_shadowing', main('for (int i = 0; i < 2; i++) {\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);\n}'), {moves: [0, 1]}],
['negative_integer_division', main('int a = -3;\na /= 2;\nplayerMove(a);'), {moves: [-1]}],
['function_return_value', 'int answer() {\nreturn 7;\n}\n' + main('int n = answer();\nplayerMove(n);'), {moves: [7]}],
['nested_if_break', main('for (int i = 0; i < 3; i++) {\nif (i == 1) {\nbreak;\n}\nplayerMove(i);\n}'), {moves: [0]}],
['floating_point', main('double x = 1.5;\ncout << x << endl;'), {output: ['1.5\n']}],
['array_index', main('int a[2] = {3, 7};\nplayerMove(a[1]);'), {moves: [7]}],
['unknown_variable', main('playerMove(missing);'), {valid: false}],
['early_return_zero', 'int stop() {\nreturn 0;\nplayerMove(9);\n}\n' + main('stop();'), {moves: []}]
];
(async () => {
const results = [];
for (const [name, code, expected] of cases) {
const actual = {valid: false, moves: [], turns: [], output: [], error: null};
context.window = {isScriptRunOver: false, cppGameAPI: {
playerMove: async n => actual.moves.push(n),
turnLeft: async n => actual.turns.push(n),
cmdPrint: text => actual.output.push(text)
}};
const validation = await validateCppSyntax(code);
actual.valid = validation.isValid;
if (!actual.valid) actual.validationErrors = validation.errors;
const executor = new CppCodeExecutor(code, null);
if (actual.valid) {
try { await executor.run(); } catch (error) {actual.error = error.message;}
}
const matchesExpected = Object.entries(expected).every(([key, value]) =>
JSON.stringify(actual[key]) === JSON.stringify(value));
results.push({name, expected, actual, matchesExpected});
}
process.stdout.write(JSON.stringify(results, null, 2) + '\n');
})().catch(error => {console.error(error); process.exitCode = 1;});

View File

@@ -21,7 +21,7 @@ function getConfigForEnv(env) {
case 'test':
return { url: 'https://test-service.001code.cn', disabledev: false };
case 'prod':
return { url: 'https://service.001code.cn', disabledev: true };
return { url: 'https://service.001code.com', disabledev: true };
case 'start':
default:
return { url: 'http://127.0.0.1:8000', disabledev: false };

View File

@@ -0,0 +1,139 @@
// Regression tests for variable scopes and return control flow in the editor interpreter.
// Run: node scripts/test-cpp-regressions.cjs
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const babel = require('@babel/core');
const parserPath = path.resolve(__dirname, '../src/components/stage-cpp/cpp-parser.js');
const source = fs.readFileSync(parserPath, 'utf8');
const compiled = babel.transformSync(source, {
filename: parserPath, configFile: false, babelrc: false,
plugins: ['@babel/plugin-transform-modules-commonjs'],
presets: process.argv.includes('--transpiled') ? ['@babel/preset-env'] : []
}).code;
const silentConsole = {log() {}, warn() {}, error() {}};
const context = {exports: {}, require, setTimeout, console: silentConsole, window: {}};
vm.runInNewContext(compiled, context, {filename: parserPath});
const {CppCodeExecutor, validateCppSyntax, createCppExecutor} = context.exports;
const main = body => `int main() {\n${body}\n}`;
// Complete program from the first email screenshot; the second repeats this issue.
const customerEmailCode = `void test() {
for (int i = 0; i < 2; i++) {
}
}
int main() {
// 在这里编写你的C++代码来控制角色
turnLeft(-1);
for (int i = 0; i < 1; i++) {
test();
playerMove(i);
}
return 0;
}`;
async function run(code, expected, configure) {
const moves = [];
const turns = [];
context.window = {isScriptRunOver: false, cppGameAPI: {
playerMove: async n => moves.push(n),
turnLeft: async n => turns.push(n),
cmdPrint: () => {}
}};
const executor = new CppCodeExecutor(code, null);
if (configure) configure(executor, context.window);
const validation = await validateCppSyntax(code);
assert.strictEqual(validation.isValid, true, JSON.stringify(validation.errors));
const exitCode = await executor.run();
if (Object.prototype.hasOwnProperty.call(expected, 'exitCode')) assert.strictEqual(exitCode, expected.exitCode);
if (expected.moves) assert.deepStrictEqual(moves, expected.moves);
if (expected.turns) assert.deepStrictEqual(turns, expected.turns);
assert.strictEqual(executor.variables, executor.globalVariables, 'scope must unwind after execution');
assert.strictEqual(executor.callStack.length, 0);
assert.strictEqual(executor.currentExecutionContext, null);
assert.strictEqual(executor.isRunning, false);
return executor;
}
const cases = [
['screenshot: main return 1 is a normal exit', main('playerMove(4);\nreturn 1;'), {moves: [4], exitCode: 1}],
['return 1 skips remaining main statements', main('return 1;\nplayerMove(9);'), {moves: [], exitCode: 1}],
['return 0 also stops main early', main('return 0;\nplayerMove(9);'), {moves: [], exitCode: 0}],
['negative exit code', main('return -1;'), {moves: [], exitCode: -1}],
['parenthesized return expression', main('int status = 2;\nreturn (status + 1);'), {moves: [], exitCode: 3}],
['main fallthrough returns zero', main('playerMove(1);'), {moves: [1], exitCode: 0}],
['return from for loop exits main', main('for (int i = 0; i < 3; i++) {\nplayerMove(i);\nreturn 1;\n}\nplayerMove(9);'), {moves: [0], exitCode: 1}],
['return zero inside loop is not ignored', main('for (int i = 0; i < 3; i++) {\nreturn 0;\n}\nplayerMove(9);'), {moves: [], exitCode: 0}],
['return through while and if exits main', main('while (true) {\nif (true) {\nreturn 2;\n}\nplayerMove(9);\n}\nplayerMove(8);'), {moves: [], exitCode: 2}],
['function return 0 skips only its own remaining statements', 'int f() {\nreturn 0;\nplayerMove(9);\n}\n' + main('f();\nplayerMove(1);\nreturn 1;'), {moves: [1], exitCode: 1}],
['function return inside loop resumes caller', 'int f() {\nfor (int i = 0; i < 3; i++) {\nreturn 1;\n}\nplayerMove(9);\n}\n' + main('f();\nplayerMove(1);'), {moves: [1], exitCode: 0}],
['identifier starting with return is not a return statement', main('int returnValue = 1;\nreturnValue = 2;\nplayerMove(returnValue);\nreturn 0;'), {moves: [2], exitCode: 0}],
['customer email complete program (screenshots 1 and 2)', customerEmailCode, {moves: [0], turns: [-1]}],
['nested loops', main('for (int i = 0; i < 2; i++) {\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);\n}'), {moves: [0, 1]}],
['loop initializer restores shadowed value', main('int i = 9;\nfor (int i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [9]}],
['loop assignment updates existing value', main('int i = 9;\nfor (i = 0; i < 2; i++) {\n}\nplayerMove(i);'), {moves: [2]}],
['block declaration versus assignment', main('int n = 1;\nif (true) {\nint n = 7;\nplayerMove(n);\n}\nif (true) {\nn = 3;\n}\nplayerMove(n);'), {moves: [7, 3]}],
['for body scope per iteration', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nplayerMove(x);\nint x = 2;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}],
['while body scope per iteration', main('int x = 9;\nint i = 0;\nwhile (i < 2) {\nplayerMove(x);\nint x = 2;\ni++;\n}\nplayerMove(x);'), {moves: [9, 9, 9]}],
['function locals', 'void f() {\nint n = 7;\nplayerMove(n);\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [7, 2]}],
['all arguments evaluated before binding', 'void f(int a, int b) {\nplayerMove(a);\nplayerMove(b);\n}\n' + main('int a = 2;\nf(7, a);\nplayerMove(a);'), {moves: [7, 2, 2]}],
['recursive locals and parameters', 'void f(int n) {\nint saved = n;\nif (n > 0) {\nf(n - 1);\n}\nplayerMove(saved);\n}\n' + main('f(2);'), {moves: [0, 1, 2]}],
['early void return unwinds scope', 'void f() {\nint n = 8;\nreturn;\n}\n' + main('int n = 2;\nf();\nplayerMove(n);'), {moves: [2]}],
['continue unwinds body scope', main('int x = 9;\nfor (int i = 0; i < 2; i++) {\nint x = 2;\ncontinue;\n}\nplayerMove(x);'), {moves: [9]}],
['break does not increment', main('int i = 0;\nfor (i = 0; i < 2; i++) {\nbreak;\n}\nplayerMove(i);'), {moves: [0]}],
];
(async () => {
for (const [name, code, expected] of cases) {
try { await run(code, expected); } catch (error) {error.message = `${name}: ${error.message}`; throw error;}
console.log(`PASS ${name}`);
}
const strings = new CppCodeExecutor('', null);
await strings.executeSimpleStatement('string msg = "outer";');
await strings.withVariableScope(async () => {
await strings.executeSimpleStatement('string msg = "inner";');
assert.strictEqual(strings.variables.get('msg'), 'inner');
});
assert.strictEqual(strings.variables.get('msg'), 'outer');
console.log('PASS string block shadowing');
// Seed the interpreter's global environment to verify lexical lookup and writes.
const globalExecutor = await run('void f() {\ng += 1;\nplayerMove(g);\n}\n' + main('int g = 9;\nf();\nplayerMove(g);'), {moves: [2, 9]}, executor => executor.variables.set('g', 1));
assert.strictEqual(globalExecutor.variables.get('g'), 2);
console.log('PASS global writes survive function calls; caller locals are not visible');
for (const stop of [false, true]) {
const executor = new CppCodeExecutor('void f(int n) {\nfor (int i = 0; i < 2; i++) {\nplayerMove(i);\n}\n}\n' + main('f(2);'), null);
executor.variables.set('n', 9);
context.window = {isScriptRunOver: false, cppGameAPI: {playerMove: async () => {
if (stop) context.window.isScriptRunOver = true;
else throw new Error('game failure');
}}};
if (stop) await executor.run();
else await assert.rejects(executor.run(), /game failure/);
assert.strictEqual(executor.variables, executor.globalVariables);
assert.strictEqual(executor.variables.get('n'), 9);
assert.strictEqual(executor.variables.has('i'), false);
assert.strictEqual(executor.callStack.length, 0);
assert.strictEqual(executor.currentExecutionContext, null);
console.log(`PASS scope cleanup on ${stop ? 'stop' : 'error'}`);
}
const highlighted = [];
context.window = {isScriptRunOver: false, setHighlightedLine: line => highlighted.push(line)};
const manager = createCppExecutor();
for (const status of [1, 0]) {
assert.strictEqual(await manager.execute(main(`return ${status};`)), status);
assert.strictEqual(manager.currentExecutor, null);
assert.strictEqual(highlighted[highlighted.length - 1], null);
}
console.log('PASS editor executor manager resolves nonzero exit and can run again');
context.window.cppGameAPI = {playerMove: async () => {throw new Error('RETURN_INTERRUPT');}};
await assert.rejects(manager.execute('void f() {\nplayerMove(1);\n}\n' + main('f();')), /RETURN_INTERRUPT/);
assert.strictEqual(manager.currentExecutor, null);
assert.strictEqual(highlighted[highlighted.length - 1], null);
console.log('PASS real game errors are not mistaken for a return signal');
console.log(`${cases.length + 6} regression checks passed`);
})().catch(error => {console.error(error); process.exitCode = 1;});

View File

@@ -0,0 +1,166 @@
// Run: npm run test:devtool
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const babel = require('@babel/core');
const root = path.resolve(__dirname, '..');
const controlPath = 'src/playground/devtool-control.js';
const compile = file => babel.transformFileSync(path.join(root, file), {
configFile: false, babelrc: false, presets: ['@babel/preset-env', '@babel/preset-react']
}).code;
const controlCode = compile(controlPath);
const homeCode = compile('src/playground/home.js');
let checks = 0;
function test(name, action) {
action();
checks++;
console.log(`PASS ${name}`);
}
function page(storage = new Map(), {enabled = true, brokenStorage = false, immediate = false} = {}) {
const starts = [];
const effects = [];
function detector(options) {
starts.push(options);
// Simulate a library probe firing during initialization with F12 already open.
if (immediate) options.ondevtoolopen('already-open');
}
detector.isSuspend = false;
const window = {
location: {href: '/editor.html'}, onbeforeunload: () => {},
cppCodeStopRun: () => effects.push('stop C++'),
loadCppProject: () => effects.push('clear C++'),
pythonCodeStopRun: () => effects.push('stop Python'),
loadPythonProject: () => effects.push('clear Python'),
unityInstance: {Quit: () => effects.push('quit engine')}
};
const sessionStorage = {
getItem(key) {if (brokenStorage) throw new Error('storage denied'); return storage.get(key) || null;},
setItem(key, value) {if (brokenStorage) throw new Error('storage denied'); storage.set(key, value);}
};
const sandbox = {
exports: {}, window, sessionStorage,
localStorage: {getItem: () => '/index.html'},
vm: {
stopAll: () => effects.push('stop Scratch'),
editingTarget: {blocks: {deleteAllBlocks: () => effects.push('clear Scratch')}},
emitWorkspaceUpdate: () => effects.push('update workspace')
},
require(name) {
if (name === 'disable-devtool') return detector;
if (name === './config.js') return {disabledev: enabled};
if (name === './devtool-control.js') return api;
throw new Error(`Unexpected import: ${name}`);
}
};
const context = vm.createContext(sandbox);
vm.runInContext(controlCode, context, {filename: controlPath});
const api = sandbox.exports;
return {api, starts, effects, detector, window, context};
}
// Use the actual options and destructive callback from each editor mode.
function stageOptions(file) {
const source = fs.readFileSync(path.join(root, file), 'utf8');
const ast = babel.parseSync(source, {configFile: false, babelrc: false, parserOpts: {plugins: ['jsx']}});
let options;
function visit(node) {
if (!node || typeof node !== 'object') return;
if (node.type === 'ImportDeclaration') {
assert.notStrictEqual(node.source.value, 'disable-devtool', `${file} bypasses shared control`);
}
if (node.type === 'CallExpression' && node.callee.name === 'initDisableDevtool') options = node.arguments[0];
for (const value of Object.values(node)) {
if (Array.isArray(value)) value.forEach(visit);
else if (value && typeof value === 'object') visit(value);
}
}
visit(ast);
assert(options, `${file} must initialize through shared control`);
// Also verify the entire JSX file remains compatible with project Babel presets.
compile(file);
return source.slice(options.start, options.end);
}
const storage = new Map();
test('home exposes stopDev and persists it for navigation', () => {
const home = page(storage);
vm.runInContext(homeCode, home.context);
home.window.stopDev();
assert.strictEqual(storage.get('stopDev'), '1');
assert.strictEqual(home.detector.isSuspend, true);
});
for (const mode of ['stage-unity', 'stage-python-unity', 'stage-cpp-unity']) {
const optionSource = stageOptions(`src/components/${mode}/stage-unity.jsx`);
test(`${mode}: home -> editor with F12 open does not start detection or clear code`, () => {
const editor = page(storage, {immediate: true});
const options = vm.runInContext(`(${optionSource})`, editor.context);
editor.api.initDisableDevtool(options);
assert.strictEqual(editor.starts.length, 0);
assert.strictEqual(editor.detector.isSuspend, true);
assert.strictEqual(editor.window.location.href, '/editor.html');
assert.deepStrictEqual(editor.effects, []);
// A refresh or another mode initialization in this tab must stay stopped.
editor.api.initDisableDevtool(options);
assert.strictEqual(editor.starts.length, 0);
});
test(`${mode}: stopDev blocks an already queued destructive callback`, () => {
const editor = page();
const options = vm.runInContext(`(${optionSource})`, editor.context);
editor.api.initDisableDevtool(options);
assert.strictEqual(editor.starts.length, 1);
const callback = editor.starts[0];
editor.window.stopDev();
assert.strictEqual(callback.ignore(), true);
callback.ondevtoolopen('queued');
assert.strictEqual(editor.window.location.href, '/editor.html');
assert.deepStrictEqual(editor.effects, []);
});
}
test('normal session retains default detection and redirect', () => {
const normal = page();
normal.api.initDisableDevtool();
assert.strictEqual(normal.starts.length, 1);
assert.strictEqual(normal.starts[0].ignore(), false);
normal.starts[0].ondevtoolopen();
assert.strictEqual(normal.window.location.href, '/index.html');
});
test('custom options, ignore and callback arguments remain intact', () => {
const normal = page();
const seen = [];
const next = () => {};
normal.api.initDisableDevtool({interval: 123, ignore: () => true, ondevtoolopen: (...args) => seen.push(args)});
assert.strictEqual(normal.starts[0].interval, 123);
assert.strictEqual(normal.starts[0].ignore(), true);
normal.starts[0].ondevtoolopen(7, next);
assert.deepStrictEqual(seen, [[7, next]]);
});
test('disabled configuration does not initialize; stopDev remains available', () => {
const development = page(new Map(), {enabled: false});
development.api.initDisableDevtool();
assert.strictEqual(development.starts.length, 0);
assert.strictEqual(typeof development.window.stopDev, 'function');
});
test('blocked storage still permits immediate stop on current page', () => {
const broken = page(new Map(), {brokenStorage: true});
broken.api.initDisableDevtool();
broken.window.stopDev();
assert.strictEqual(broken.detector.isSuspend, true);
assert.strictEqual(broken.starts[0].ignore(), true);
broken.starts[0].ondevtoolopen();
broken.api.initDisableDevtool();
assert.strictEqual(broken.starts.length, 1);
assert.strictEqual(broken.window.location.href, '/editor.html');
});
for (const file of ['src/playground/competition.js', 'src/playground/competition_sum.js']) {
const optionsSource = stageOptions(file);
test(`${file}: same stopDev state is honored`, () => {
const competition = page(storage, {immediate: true});
competition.api.initDisableDevtool(vm.runInContext(`(${optionsSource})`, competition.context));
assert.strictEqual(competition.starts.length, 0);
});
}
console.log(`${checks} devtool regression checks passed`);

View File

@@ -0,0 +1,49 @@
.overlay {
position: absolute;
inset: 0;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(8, 8, 8, 0.92);
pointer-events: auto;
opacity: 1;
transition: opacity 0.28s ease-out, visibility 0.28s ease-out;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
visibility: hidden;
}
.text {
color: #f2f2f2;
font-size: 15px;
line-height: 1.5;
letter-spacing: 0.5px;
}
.barTrack {
width: min(72%, 280px);
height: 10px;
background: rgba(255, 255, 255, 0.16);
border-radius: 999px;
overflow: hidden;
}
.barFill {
height: 100%;
background: linear-gradient(90deg, #4fc3f7 0%, #29b6f6 100%);
border-radius: 999px;
transition: width 0.25s linear;
}
.percent {
color: #e0e0e0;
font-size: 18px;
font-weight: 600;
font-variant-numeric: tabular-nums;
}

View File

@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './game-loading-overlay.css';
const GameLoadingOverlay = ({ visible, progress }) => {
const pct = Math.max(0, Math.min(100, Math.round(Number(progress) || 0)));
return (
<div
className={`${styles.overlay}${visible ? '' : ` ${styles.hidden}`}`}
aria-hidden={!visible}
>
<div className={styles.text}>正在加载中</div>
<div className={styles.barTrack}>
<div className={styles.barFill} style={{ width: `${pct}%` }} />
</div>
<div className={styles.percent}>{pct}%</div>
</div>
);
};
GameLoadingOverlay.propTypes = {
visible: PropTypes.bool,
progress: PropTypes.number
};
GameLoadingOverlay.defaultProps = {
visible: true,
progress: 0
};
export default GameLoadingOverlay;

View File

@@ -0,0 +1,47 @@
import { useCallback, useEffect, useRef, useState } from 'react';
const alreadyReady = () => typeof window !== 'undefined' && window.iscreate === 1;
const useGameLoadProgress = () => {
const [progress, setProgress] = useState(() => (alreadyReady() ? 100 : 0));
const [visible, setVisible] = useState(() => !alreadyReady());
const hidingRef = useRef(false);
useEffect(() => {
if (!visible) return undefined;
const timer = setInterval(() => {
if (window.iscreate === 1) {
if (!hidingRef.current) {
hidingRef.current = true;
setProgress(100);
setTimeout(() => setVisible(false), 280);
}
clearInterval(timer);
return;
}
setProgress(prev => (prev >= 90 && prev < 99 ? prev + 1 : prev));
}, 300);
return () => clearInterval(timer);
}, [visible]);
const onUnityProgress = useCallback(unityProgress => {
const mapped = Math.round(Math.min(90, Number(unityProgress) * 90));
if (!Number.isFinite(mapped)) return;
setProgress(prev => (mapped > prev ? mapped : prev));
}, []);
const onUnityInstanceCreated = useCallback(() => {
setProgress(prev => Math.max(prev, 90));
}, []);
return {
progress,
visible,
onUnityProgress,
onUnityInstanceCreated
};
};
export default useGameLoadProgress;

View File

@@ -235,52 +235,70 @@
}
.startButton {
background-color: white;
color: inherit;
background: linear-gradient(135deg, #22c55e, #16a34a);
color: #fff;
border: none;
padding: 10px 20px;
padding: 10px 22px;
text-align: center;
text-decoration: none;
display: inline-block;
/* font-size: 16px; */
font-weight: 600;
font-size: 14px;
margin: 4px 2px;
cursor: pointer;
transition: background-color 0.3s, transform 0.2s, box-shadow 0.3s;
border-radius: 4px;
animation: pulse 2s infinite; /* 添加吸引注意的动画 */
transition: all 0.3s ease;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(34, 197, 94, 0.3);
letter-spacing: 0.5px;
}
.startButton:hover {
background-color: #56b9b7; /* 悬停时颜色加深 */
transform: scale(1.05); /* 悬停时略微放大 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 添加阴影 */
background: linear-gradient(135deg, #16a34a, #15803d);
transform: scale(1.04);
box-shadow: 0 4px 16px rgba(34, 197, 94, 0.45);
}
.startButton:active {
background-color: #279a98; /* 点击时颜色进一步加深 */
transform: scale(0.98); /* 点击时略微缩小 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); /* 减少阴影 */
}
/* 定义吸引注意的动画 */
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
background: linear-gradient(135deg, #15803d, #166534);
transform: scale(0.97);
box-shadow: 0 2px 6px rgba(34, 197, 94, 0.35);
}
/* 可选:在点击后停止动画并改变样式 */
.startButtonClicked {
background-color: #ff9800; /* 点击后颜色变为橙色 */
transform: scale(1.1); /* 点击后稍微放大 */
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); /* 增加阴影 */
animation: none; /* 停止动画 */
background: linear-gradient(135deg, #f59e0b, #d97706);
transform: scale(1.05);
box-shadow: 0 6px 12px rgba(245, 158, 11, 0.4);
}
.interruptButton {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: #fff;
border: none;
padding: 10px 22px;
text-align: center;
text-decoration: none;
display: inline-block;
font-weight: 600;
font-size: 14px;
margin: 4px 8px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
letter-spacing: 0.5px;
}
.interruptButton:hover {
background: linear-gradient(135deg, #dc2626, #b91c1c);
transform: scale(1.04);
box-shadow: 0 4px 16px rgba(239, 68, 68, 0.45);
}
.interruptButton:active {
background: linear-gradient(135deg, #b91c1c, #991b1b);
transform: scale(0.97);
box-shadow: 0 2px 6px rgba(239, 68, 68, 0.35);
}
.lvcount{

View File

@@ -246,8 +246,10 @@ class MenuBar extends React.Component {
'restoreOptionMessage',
'handleClickStart',
'handleClickStopAll',
'handleClickInterrupt',
'handleFeedbackClick',
'handleCloseModal',
'handleOpenNoPermissionModal',
'handleCloseVideoModal',
'handlelearnVideoClick',
'handleVideoLoaded',
@@ -262,6 +264,7 @@ class MenuBar extends React.Component {
showAntiAddictionMsg: false,
antiAddictionMsg: '',
showModal: false,
showNoPermissionModal: false,
showVideoModal: false,
currentLevel: window.gamelvdata ? window.gamelvdata.currentlv : '',
currentGameId: window.gamelvdata ? window.gamelvdata.gameid : '',
@@ -582,9 +585,15 @@ class MenuBar extends React.Component {
window.vm.stopAll();
}
handleOpenNoPermissionModal() {
this.setState({ showNoPermissionModal: true });
}
handleCloseModal() {
// 设置 showModal 为 false 以隐藏模态窗口
this.setState({ showModal: false });
this.setState({ showModal: false, showNoPermissionModal: false });
if (window.location.hash === '#carousel_ac25') {
history.replaceState(null, document.title, window.location.pathname + window.location.search);
}
}
// 打开视频模态窗口
@@ -875,6 +884,43 @@ class MenuBar extends React.Component {
}
handleClickInterrupt() {
console.log('取消执行按钮点击');
// 1. 终止所有代码执行
try { this.props.vm.stopAll(); } catch (e) { /* ignore */ }
try { if (window.pythonCodeStopRun) window.pythonCodeStopRun(); } catch (e) { /* ignore */ }
try { if (window.cppCodeStopRun) window.cppCodeStopRun(); } catch (e) { /* ignore */ }
// 2. 强制重置执行状态
window.isPythonRun = false;
window.isScriptRunOver = true;
// 3. 重置 Unity 游戏到初始状态(重新加载当前关卡)
if (window.unityInstance && window.gamelvdataJson) {
try {
let realLvId_tmp = '';
if (localStorage.getItem('platformMode') === 'normal') {
realLvId_tmp = CONFIG.getRealLvId(
window.gamelvdataJson.gamelv,
window.gamelvdataJson.gameid,
window.gamelvdataJson.ismatch,
window.gamelvdataJson.gameNumber,
window.gamelvdataJson.gamemode
) + (window.gamelvdata ? window.gamelvdata.currentlv : '');
} else {
realLvId_tmp = CONFIG.getRealLvId(
window.gamelvdataJson.gamelv,
window.gamelvdataJson.gameid,
window.gamelvdataJson.ismatch,
window.gamelvdataJson.gameNumber
);
}
window.unityInstance.SendMessage("GameController", "SwitchLevel", realLvId_tmp);
console.log('Unity 关卡重置:', realLvId_tmp);
} catch (e) {
console.warn('Unity 重置失败:', e);
}
}
}
handleChangeSkin() {
unityInstance.SendMessage("Player", "CallChangeSkin", 1);
}
@@ -1341,6 +1387,18 @@ class MenuBar extends React.Component {
</a>
</div> */}
<div className={styles.accountInfoGroup}>
<a
className={styles.feedbackLink}
rel="noopener noreferrer"
target="_blank"
>
<Button className={styles.interruptButton} onClick={this.handleClickInterrupt}>
取消执行
</Button>
</a>
</div>
<div className={styles.accountInfoGroup}>
<a
className={styles.feedbackLink}
@@ -1436,9 +1494,16 @@ class MenuBar extends React.Component {
{aboutButton}
<a href="#carousel_ac25" className='u-dialog-link' id='noPermissionModal'></a>
<a
href="#carousel_ac25"
className='u-dialog-link'
id='noPermissionModal'
onClick={this.handleOpenNoPermissionModal}
></a>
<a id='competitionLevelChoose_dialog' className="u-dialog-link" href="#carousel_d668"></a>
<NoPermissionModal onCancel={this.handleCloseModal} />
{this.state.showNoPermissionModal ? (
<NoPermissionModal onCancel={this.handleCloseModal} />
) : null}
{enable_screen_block && (
<div style={{

View File

@@ -126,6 +126,7 @@ const NoPermissionModal = ({ onCancel }) => {
u-container-align-center-xl
u-container-style
u-dialog-block
u-dialog-open
u-opacity
u-opacity-70
u-valign-middle-lg

View File

@@ -11,8 +11,11 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
// const friendToggle = new FriendToggle();
window.RankBadgeGenerator = RankBadgeGenerator;
window.CONFIG = CONFIG;
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -46,6 +49,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -547,7 +560,7 @@ const UnityComponent = function (props) {
useEffect(() => {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
@@ -1272,8 +1285,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1337,6 +1351,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />

View File

@@ -510,10 +510,57 @@ export class CppSyntaxValidator {
}
}
// 声明写入当前作用域,赋值更新最近的已有变量;函数不继承调用者的局部变量。
class CppVariableScope extends Map {
constructor(parent = null) {
super();
this.parent = parent;
}
declare(name, value) {
return super.set(name, value);
}
has(name) {
return super.has(name) || Boolean(this.parent && this.parent.has(name));
}
get(name) {
return super.has(name) ? super.get(name) : this.parent && this.parent.get(name);
}
set(name, value) {
if (!super.has(name) && this.parent && this.parent.has(name)) {
this.parent.set(name, value);
return this;
}
return super.set(name, value);
}
*entries() {
const visible = new Map(this.parent ? this.parent.entries() : []);
for (const [name, value] of super.entries()) visible.set(name, value);
yield* visible.entries();
}
[Symbol.iterator]() {
return this.entries();
}
}
// return 是跨越循环/条件块的控制信号,只在所属函数边界消费。
// 使用独立类型,避免吞掉恰好带有相同消息的真实运行错误。
class CppReturnSignal {
constructor(value) {
this.value = value;
}
}
export class CppCodeExecutor {
constructor(code, setHighlightedLine) {
this.code = code;
this.variables = new Map();
this.globalVariables = new CppVariableScope();
this.variables = this.globalVariables;
this.functions = new Map(); // 存储自定义函数
this.currentLine = 0;
this.lines = [];
@@ -527,7 +574,7 @@ export class CppCodeExecutor {
try {
this.isRunning = true;
this.parseCode();
await this.executeMain();
return await this.withVariableScope(() => this.executeMain(), this.globalVariables);
} catch (error) {
console.error('C++执行错误:', error);
throw error;
@@ -544,6 +591,16 @@ export class CppCodeExecutor {
return !this.isRunning || window.isScriptRunOver;
}
async withVariableScope(action, parent = this.variables) {
const previous = this.variables;
this.variables = new CppVariableScope(parent);
try {
return await action();
} finally {
this.variables = previous;
}
}
parseCode() {
debugLog('🔍 开始解析C++代码,支持自定义函数');
@@ -765,12 +822,26 @@ export class CppCodeExecutor {
while (i < lines.length) {
const line = lines[i].trim();
if (!line || line.startsWith('//') || line === '{' || line === '}' || line === 'return 0;' ||
if (!line || line.startsWith('//') || line === '{' || line === '}' ||
line.match(/^\s*}\s*else\s*\{\s*$/) || line.startsWith('else')) {
i++;
continue;
}
// return 不能标记成 continue否则循环会直接跳过它。
if (/^return\b/.test(line)) {
statements.push({
type: 'control',
subtype: 'return',
content: line,
lineNumber: isMainFunction ? this.getActualLineNumber(i) : i + 1,
originalContent: line,
contextPath: isMainFunction ? 'main' : 'function'
});
i++;
continue;
}
// 检查是否是自定义函数调用(包括带括号的实参,如 f((n + 1));
const customCall = this.parseCustomFunctionCall(line);
if (customCall) {
@@ -799,8 +870,8 @@ export class CppCodeExecutor {
statements.push(whileLoop);
i = whileLoop.endIndex;
} else {
// 检查是否是break或continue或return语句
if (line === 'break;' || line === 'continue;' || line.startsWith('return')) {
// 检查是否是break或continue语句
if (line === 'break;' || line === 'continue;') {
statements.push({
type: 'control',
subtype: line === 'break;' ? 'break' : 'continue',
@@ -1278,10 +1349,17 @@ export class CppCodeExecutor {
}
async executeMain() {
for (const statement of this.statements) {
if (this.shouldStop()) break;
await this.executeStatement(statement);
try {
for (const statement of this.statements) {
if (this.shouldStop()) break;
await this.executeStatement(statement);
}
} catch (signal) {
if (!(signal instanceof CppReturnSignal)) throw signal;
return signal.value;
}
// main 正常执行到末尾,相当于 return 0。
return 0;
}
async executeStatement(statement) {
@@ -1300,10 +1378,10 @@ export class CppCodeExecutor {
await this.executeSimpleStatement(statement.content);
break;
case 'for':
await this.executeForLoop(statement);
await this.withVariableScope(() => this.executeForLoop(statement));
break;
case 'if':
await this.executeIfStatement(statement);
await this.withVariableScope(() => this.executeIfStatement(statement));
break;
case 'while':
await this.executeWhileLoop(statement);
@@ -1312,8 +1390,11 @@ export class CppCodeExecutor {
await this.executeFunctionCall(statement);
break;
case 'control':
if (statement.content.startsWith('return')) {
throw new Error('RETURN_INTERRUPT');
if (statement.subtype === 'return') {
const match = statement.content.match(/^return\b([\s\S]*?);(?:\s*\/\/.*)?$/);
if (!match) throw new Error(`无法解析return语句: ${statement.content}`);
const expression = match[1].trim();
throw new CppReturnSignal(expression ? this.evaluateExpression(expression) : undefined);
}
// break和continue在各自的循环中处理这里只记录
debugLog(`🔄 控制语句: ${statement.subtype}`);
@@ -1324,6 +1405,14 @@ export class CppCodeExecutor {
async executeSimpleStatement(content) {
debugLog(`🔍 执行简单语句: ${content}`);
// 声明必须留在当前作用域,不能按普通赋值覆盖外层同名变量。
const declaration = content.match(/^\s*(int|bool|Position|string)\s+(\w+)\s*=\s*(.+);\s*$/);
if (declaration) {
const [, , name, value] = declaration;
this.variables.declare(name, this.evaluateExpression(value));
return;
}
// 检查是否是自定义函数调用(包括带括号的实参,如 f((n + 1));
const customCall = this.parseCustomFunctionCall(content);
if (customCall && this.functions.has(customCall.functionName)) {
@@ -1355,40 +1444,6 @@ export class CppCodeExecutor {
}
}
// 变量声明
if (content.includes('int ') && content.includes('=')) {
const match = content.match(/int\s+(\w+)\s*=\s*(.+?);/);
if (match) {
const [, varName, value] = match;
this.variables.set(varName, this.evaluateExpression(value));
debugLog('声明int变量 ' + varName + ' = ' + this.variables.get(varName));
return;
}
}
// bool类型变量声明
if (content.includes('bool ') && content.includes('=')) {
const match = content.match(/bool\s+(\w+)\s*=\s*(.+?);/);
if (match) {
const [, varName, value] = match;
this.variables.set(varName, this.evaluateExpression(value));
debugLog('声明bool变量 ' + varName + ' = ' + this.variables.get(varName));
return;
}
}
// Position类型变量声明
if (content.includes('Position ') && content.includes('=')) {
const match = content.match(/Position\s+(\w+)\s*=\s*(.+?);/);
if (match) {
const [, varName, value] = match;
const position = this.evaluateExpression(value);
this.variables.set(varName, position);
debugLog(`声明Position变量 ${varName}:`, position);
return;
}
}
// 复合赋值运算符 (+=, -=, *=, /=)
if (content.includes('+=')) {
const match = content.match(/(\w+)\s*\+=\s*(.+?);/);
@@ -1553,41 +1608,46 @@ export class CppCodeExecutor {
let shouldContinue = false;
for (const bodyStatement of forStatement.body) {
if (this.shouldStop()) break;
try {
await this.withVariableScope(async () => {
for (const bodyStatement of forStatement.body) {
if (this.shouldStop()) break;
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出for循环');
shouldBreak = true;
break;
}
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出for循环');
shouldBreak = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
}
});
} finally {
this.currentExecutionContext = oldContext;
}
this.currentExecutionContext = oldContext;
if (this.shouldStop()) break;
if (this.shouldStop() || shouldBreak) break;
// 如果遇到continue跳过执行增量直接进入下次循环
if (shouldContinue) {
@@ -1730,43 +1790,48 @@ export class CppCodeExecutor {
let shouldContinue = false;
for (const bodyStatement of whileStatement.body) {
if (this.shouldStop()) break;
try {
await this.withVariableScope(async () => {
for (const bodyStatement of whileStatement.body) {
if (this.shouldStop()) break;
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出while循环');
shouldBreak = true;
break;
}
// 检查是否是break或continue语句
if ((bodyStatement.content && bodyStatement.content.trim() === 'break;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'break')) {
debugLog('🔄 遇到break语句退出while循环');
shouldBreak = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
if ((bodyStatement.content && bodyStatement.content.trim() === 'continue;') ||
(bodyStatement.type === 'control' && bodyStatement.subtype === 'continue')) {
debugLog('🔄 遇到continue语句跳过本次循环剩余部分');
shouldContinue = true;
break;
}
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
let adjustedStatement;
if (bodyStatement.exactLineNumber) {
adjustedStatement = {
...bodyStatement,
lineNumber: bodyStatement.exactLineNumber
};
debugLog(`🎯 使用精确行号: "${bodyStatement.content}" -> 第${bodyStatement.exactLineNumber}`);
} else {
adjustedStatement = this.adjustStatementLineNumber.call(this, bodyStatement);
}
await this.executeStatement(adjustedStatement);
// 在每个语句执行后重新检查循环条件(特别重要!)
debugLog(`🔍 语句执行后重新检查条件: ${whileStatement.condition} = ${this.evaluateCondition(whileStatement.condition)}`);
// 在每个语句执行后重新检查循环条件(特别重要!)
debugLog(`🔍 语句执行后重新检查条件: ${whileStatement.condition} = ${this.evaluateCondition(whileStatement.condition)}`);
}
});
} finally {
this.currentExecutionContext = oldContext;
}
this.currentExecutionContext = oldContext;
if (shouldContinue) {
continue; // 跳过本次循环的剩余部分,重新开始
}
@@ -1804,6 +1869,11 @@ export class CppCodeExecutor {
throw new Error(`函数调用栈溢出: ${functionName} (可能存在无限递归)`);
}
// 所有实参先在调用者作用域求值,避免前一个形参覆盖后一个实参。
const argumentValues = callArguments.map(arg => this.evaluateExpression(arg));
const callerVariables = this.variables;
this.variables = new CppVariableScope(this.globalVariables);
// 将函数调用信息压入调用栈
this.callStack.push({
functionName: functionName,
@@ -1812,21 +1882,14 @@ export class CppCodeExecutor {
});
try {
// 最小改动:防递归数据污染,只备份会被覆盖的同名参数变量
const savedParams = new Map();
for (let i = 0; i < functionInfo.parameters.length; i++) {
const pName = functionInfo.parameters[i].name;
if (this.variables.has(pName)) savedParams.set(pName, this.variables.get(pName));
}
// 设置函数参数变量
for (let i = 0; i < functionInfo.parameters.length; i++) {
const param = functionInfo.parameters[i];
const argValue = this.evaluateExpression(callArguments[i]);
const argValue = argumentValues[i];
// 类型检查和转换
const convertedValue = this.convertArgumentToType(argValue, param.type);
this.variables.set(param.name, convertedValue);
this.variables.declare(param.name, convertedValue);
console.log(`📋 设置参数: ${param.name} (${param.type}) = ${convertedValue}`);
}
@@ -1856,22 +1919,14 @@ export class CppCodeExecutor {
await this.executeStatement(adjustedStatement);
}
} catch (e) {
if (e.message !== 'RETURN_INTERRUPT') throw e;
}
// 最小改动:仅恢复递归调用前的参数状态,保留全局变量的修改
for (let i = 0; i < functionInfo.parameters.length; i++) {
const pName = functionInfo.parameters[i].name;
if (savedParams.has(pName)) {
this.variables.set(pName, savedParams.get(pName));
} else {
this.variables.delete(pName);
}
if (!(e instanceof CppReturnSignal)) throw e;
return e.value;
}
console.log(`✅ 函数${functionName}执行完成`);
} finally {
this.variables = callerVariables;
// 从调用栈中弹出当前函数
const callInfo = this.callStack.pop();
const executionTime = Date.now() - callInfo.startTime;
@@ -2386,202 +2441,162 @@ export class CppCodeExecutor {
return position;
}
// 字符串表达式
if (expr.includes('"') || expr.includes("'")) {
return expr.replace(/['"]/g, '');
// 整个表达式是字符串字面量时才当字符串;含引号的 API 调用走后面的算术解析
if ((expr.startsWith('"') && expr.endsWith('"')) ||
(expr.startsWith("'") && expr.endsWith("'"))) {
return expr.slice(1, -1);
}
// 使用改进的表达式解析器处理复杂表达式
return this.parseComplexExpression(expr);
}
// 新增:改进的表达式解析器,支持括号嵌套和运算优先级
// 递归下降:一元 +/- ,然后 * / % 优先于 + -
parseComplexExpression(expr) {
try {
// 移除所有空格
expr = expr.replace(/\s+/g, '');
// 处理括号
while (expr.includes('(')) {
const lastOpenParen = expr.lastIndexOf('(');
const closeParen = expr.indexOf(')', lastOpenParen);
if (closeParen === -1) {
debugLog(`⚠️ 括号不匹配: ${expr}`);
return 0;
}
const innerExpr = expr.substring(lastOpenParen + 1, closeParen);
const innerResult = this.parseComplexExpression(innerExpr);
expr = expr.substring(0, lastOpenParen) + innerResult + expr.substring(closeParen + 1);
}
// 处理一元运算符(负号)
expr = this.handleUnaryOperators(expr);
// 按正确的运算优先级解析:
// 加法和减法是最低优先级,因此在此处最先切分。
// 切分后的每一项将交给 parseAdditionSubtraction在其中处理更高级别的乘除法。
return this.parseAdditionSubtraction(expr);
this._exprTokens = this.lexArithmetic(expr);
this._exprPos = 0;
if (this._exprTokens.length === 0) return 0;
const result = this.parseAddExpr();
return typeof result === 'number' && !Number.isNaN(result) ? result : 0;
} catch (error) {
debugLog(`⚠️ 表达式解析错误: ${expr}, 错误: ${error.message}`);
return 0;
}
}
// 处理一元运算符
handleUnaryOperators(expr) {
// 先处理开头的负号
if (expr.startsWith('-')) {
const restExpr = expr.substring(1);
if (/^\d+/.test(restExpr)) {
// 负数
const match = restExpr.match(/^\d+/);
const number = match[0];
const remaining = restExpr.substring(number.length);
return (-parseInt(number)) + remaining;
} else if (/^[a-zA-Z_]\w*/.test(restExpr)) {
// 负变量
const match = restExpr.match(/^[a-zA-Z_]\w*/);
const varName = match[0];
const remaining = restExpr.substring(varName.length);
if (this.variables.has(varName)) {
return (-this.variables.get(varName)) + remaining;
}
}
}
return expr;
}
// 解析乘法、除法和模运算(高优先级)
parseMultiplicationDivision(expr) {
const tokens = this.tokenizeExpression(expr, ['*', '/', '%']);
let result = this.evaluateToken(tokens[0]);
for (let i = 1; i < tokens.length; i += 2) {
const operator = tokens[i];
const operand = this.evaluateToken(tokens[i + 1]);
if (operator === '*') {
result *= operand;
} else if (operator === '/') {
result = Math.floor(result / operand); // C++整数除法
} else if (operator === '%') {
result = result % operand; // 模运算
}
}
return result;
}
// 解析加法和减法(低优先级)
parseAdditionSubtraction(expr) {
const tokens = this.tokenizeExpressionSmart(expr, ['+', '-']);
let result = this.evaluateToken(tokens[0]);
for (let i = 1; i < tokens.length; i += 2) {
const operator = tokens[i];
const operand = this.evaluateToken(tokens[i + 1]);
if (operator === '+') {
result += operand;
} else if (operator === '-') {
result -= operand;
}
}
return result;
}
// 智能分词器,考虑到负号可能是一元运算符
tokenizeExpressionSmart(expr, operators) {
lexArithmetic(expr) {
const tokens = [];
let current = '';
let i = 0;
const isIdChar = (ch) => /[a-zA-Z0-9_.]/.test(ch);
while (i < expr.length) {
const char = expr[i];
if (operators.includes(char)) {
// 检查是否是一元负号
if (char === '-' && (i === 0 || operators.includes(expr[i-1]) || expr[i-1] === '(')) {
// 这是一元负号,继续收集
current += char;
} else {
// 这是二元运算符
if (current.trim()) {
tokens.push(current.trim());
current = '';
}
tokens.push(char);
const ch = expr[i];
if (/\s/.test(ch)) {
i++;
continue;
}
if ('+-*/%()'.includes(ch)) {
tokens.push({ type: ch });
i++;
continue;
}
if (/\d/.test(ch)) {
let num = '';
while (i < expr.length && /\d/.test(expr[i])) {
num += expr[i++];
}
} else {
current += char;
tokens.push({ type: 'num', value: parseInt(num, 10) });
continue;
}
if (/[a-zA-Z_]/.test(ch)) {
let id = '';
while (i < expr.length && isIdChar(expr[i])) {
id += expr[i++];
}
if (expr[i] === '(') {
let depth = 0;
let call = id;
while (i < expr.length) {
const c = expr[i++];
call += c;
if (c === '(') depth++;
else if (c === ')') {
depth--;
if (depth === 0) break;
}
}
tokens.push({ type: 'call', value: call });
} else {
tokens.push({ type: 'id', value: id });
}
continue;
}
i++;
}
if (current.trim()) {
tokens.push(current.trim());
}
return tokens;
}
// 将表达式分解为标记
tokenizeExpression(expr, operators) {
const tokens = [];
let current = '';
peekExprToken() {
return this._exprTokens[this._exprPos] || { type: 'eof' };
}
for (let i = 0; i < expr.length; i++) {
const char = expr[i];
consumeExprToken() {
return this._exprTokens[this._exprPos++] || { type: 'eof' };
}
if (operators.includes(char)) {
if (current.trim()) {
tokens.push(current.trim());
current = '';
}
tokens.push(char);
parseAddExpr() {
let left = this.parseMulExpr();
while (this.peekExprToken().type === '+' || this.peekExprToken().type === '-') {
const op = this.consumeExprToken().type;
const right = this.parseMulExpr();
left = op === '+' ? left + right : left - right;
}
return left;
}
parseMulExpr() {
let left = this.parseUnaryExpr();
while (['*', '/', '%'].includes(this.peekExprToken().type)) {
const op = this.consumeExprToken().type;
const right = this.parseUnaryExpr();
if (op === '*') {
left *= right;
} else if (op === '/') {
left = right === 0 ? 0 : Math.trunc(left / right);
} else {
current += char;
left %= right;
}
}
if (current.trim()) {
tokens.push(current.trim());
}
return tokens;
return left;
}
// 计算单个标记的值
evaluateToken(token) {
token = token.trim();
if (!token) return 0;
// 如果包含乘除法或模运算,交给乘除法解析器处理
if (token.includes('*') || token.includes('/') || token.includes('%')) {
return this.parseMultiplicationDivision(token);
parseUnaryExpr() {
const type = this.peekExprToken().type;
if (type === '-') {
this.consumeExprToken();
return -this.parseUnaryExpr();
}
if (type === '+') {
this.consumeExprToken();
return this.parseUnaryExpr();
}
return this.parsePrimaryExpr();
}
// 布尔常量
parsePrimaryExpr() {
const token = this.peekExprToken();
if (token.type === 'num') {
this.consumeExprToken();
return token.value;
}
if (token.type === 'id') {
this.consumeExprToken();
return this.evaluateAtom(token.value);
}
if (token.type === 'call') {
this.consumeExprToken();
return this.evaluateAtom(token.value);
}
if (token.type === '(') {
this.consumeExprToken();
const value = this.parseAddExpr();
if (this.peekExprToken().type === ')') {
this.consumeExprToken();
}
return value;
}
debugLog(`⚠️ 无法解析标记: ${token.type}`);
return 0;
}
evaluateAtom(token) {
if (token === 'true') return 1;
if (token === 'false') return 0;
// 数字
if (/^-?\d+$/.test(token)) {
return parseInt(token);
}
// 变量查找
if (this.variables.has(token)) {
return this.variables.get(token);
}
// 对象属性访问 (如 pos.x, pos.y, pos.z)
if (token.includes('.')) {
const [objName, propName] = token.split('.');
if (this.variables.has(objName)) {
@@ -2592,7 +2607,6 @@ export class CppCodeExecutor {
}
}
// 游戏API调用
if (token.includes('player_data(')) {
const match = token.match(/player_data\s*\(\s*"(.+?)"\s*\)/);
if (match) return window.cppGameAPI.player_data(match[1]);
@@ -2738,7 +2752,7 @@ export const createCppExecutor = () => {
const executor = new CppCodeExecutor(code, window.setHighlightedLine);
this.currentExecutor = executor;
try {
await executor.run();
return await executor.run();
} finally {
this.currentExecutor = null;
}

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,13 @@ const StageWrapperCppClang = () => {
))}
</div>
</div>
<div
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
aria-hidden={isApiPanelReady}
>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>正在加载中完成后即可插入代码</div>
</div>
</div>
);
};
@@ -1196,6 +1187,12 @@ const StageWrapperCppClang = () => {
// 移除snippet标记转换为普通文本
insertCode = code.replace(/#{([^}]*)}/g, '$1');
}
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
insertCode = '\n' + insertCode;
}
if (!insertCode.endsWith('\n')) {
insertCode += '\n';
}
// 插入代码
view.dispatch({
@@ -1209,10 +1206,6 @@ const StageWrapperCppClang = () => {
}
});
// 关闭API参考窗口
setApiDrawerOpen(false);
// 聚焦编辑器
view.focus();
}
}
@@ -1223,166 +1216,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,27 +503,63 @@
}
}
/* 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;
}
.apiDrawer.apiDrawerOpen {
right: 0;
/* 滑出显示 */
.apiDrawerLoading {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(18, 18, 18, 0.82);
backdrop-filter: blur(2px);
cursor: not-allowed;
}
.apiDrawerLoadingHidden {
display: none;
}
.apiDrawerLoadingSpinner {
width: 38px;
height: 38px;
border: 3px solid rgba(255, 255, 255, 0.18);
border-top-color: #ba68c8;
border-radius: 50%;
animation: apiDrawerLoadingSpin 0.9s linear infinite;
will-change: transform;
}
.apiDrawerLoadingText {
color: #e0e0e0;
font-size: 13px;
line-height: 1.7;
text-align: center;
padding: 0 18px;
}
@keyframes apiDrawerLoadingSpin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.apiDrawerHeader {
@@ -526,21 +579,6 @@
font-weight: 600;
}
.apiDrawerClose {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
padding: 5px;
border-radius: 3px;
transition: background-color 0.2s ease;
}
.apiDrawerClose:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.apiDrawerContent {
flex: 1;
overflow: hidden;
@@ -710,11 +748,6 @@
@media (max-width: 1200px) {
.apiDrawer {
width: 25vw;
right: -25vw;
}
.apiDrawer.apiDrawerOpen {
right: 0;
}
.apiDrawerCategoryButton {
@@ -730,11 +763,6 @@
@media (max-width: 900px) {
.apiDrawer {
width: 35vw;
right: -35vw;
}
.apiDrawer.apiDrawerOpen {
right: 0;
}
.apiDrawerHeader h3 {

View File

@@ -11,8 +11,11 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
// const friendToggle = new FriendToggle();
window.RankBadgeGenerator = RankBadgeGenerator;
window.CONFIG = CONFIG;
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -46,6 +49,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -547,7 +560,7 @@ const UnityComponent = function (props) {
useEffect(() => {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
@@ -1257,8 +1270,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1322,6 +1336,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />

View File

@@ -139,103 +139,79 @@
background-color: #0056b3;
}
/* API抽屉标签样式 */
.apiDrawerTab {
position: fixed;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 45px;
height: 90px;
background: hsla(215, 100%, 65%, 1);
border: 1px solid hsla(215, 100%, 65%, 1);
border-right: none;
border-radius: 12px 0 0 12px;
cursor: pointer;
z-index: 1001;
/* API参考 + 编辑器并排:参考在左,编辑器靠右 */
.editorWithApi {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: -4px 0 12px rgba(0, 0, 0, 0.25);
color: #BDC3C7;
backdrop-filter: blur(10px);
flex-direction: row;
height: 100%;
width: 54vw;
overflow: hidden;
}
.apiDrawerTab:hover {
background: hsla(215, 100%, 65%, 1);
transform: translateY(-50%) translateX(-3px);
box-shadow: -6px 0 18px rgba(0, 0, 0, 0.35);
color: #FFFFFF;
.editorPane {
flex: 1;
min-width: 0;
height: 100%;
position: relative;
overflow: auto;
}
.apiDrawerTab.apiDrawerTabOpen {
background: linear-gradient(135deg, #D35400 0%, #E67E22 100%);
border-color: #E67E22;
right: 20vw;
color: #FFFFFF;
}
.apiDrawerTab.apiDrawerTabOpen:hover {
background: linear-gradient(135deg, #E67E22 0%, #F39C12 100%);
color: #FFFFFF;
}
.apiDrawerTabIcon {
font-size: 14px;
font-weight: bold;
line-height: 1;
color: #FFFFFF;
opacity: 0.9;
transition: all 0.2s ease;
}
.apiDrawerTab:hover .apiDrawerTabIcon {
opacity: 1;
transform: scale(1.1);
}
.apiDrawerTabText {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.5px;
writing-mode: vertical-lr;
text-orientation: mixed;
line-height: 1;
color: #FFFFFF;
opacity: 0.8;
transition: all 0.2s ease;
}
.apiDrawerTab:hover .apiDrawerTabText {
opacity: 1;
letter-spacing: 2px;
}
/* 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;
}
.apiDrawer.apiDrawerOpen {
right: 0;
/* 滑出显示 */
.apiDrawerLoading {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: rgba(18, 18, 18, 0.82);
backdrop-filter: blur(2px);
cursor: not-allowed;
}
.apiDrawerLoadingHidden {
display: none;
}
.apiDrawerLoadingSpinner {
width: 38px;
height: 38px;
border: 3px solid rgba(255, 255, 255, 0.18);
border-top-color: #ba68c8;
border-radius: 50%;
animation: apiDrawerLoadingSpin 0.9s linear infinite;
will-change: transform;
}
.apiDrawerLoadingText {
color: #e0e0e0;
font-size: 13px;
line-height: 1.7;
text-align: center;
padding: 0 18px;
}
@keyframes apiDrawerLoadingSpin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.apiDrawerHeader {
@@ -255,21 +231,6 @@
font-weight: 600;
}
.apiDrawerClose {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
padding: 5px;
border-radius: 3px;
transition: background-color 0.2s ease;
}
.apiDrawerClose:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.apiDrawerContent {
flex: 1;
overflow: hidden;
@@ -440,15 +401,6 @@
@media (max-width: 1200px) {
.apiDrawer {
width: 25vw;
right: -25vw;
}
.apiDrawer.apiDrawerOpen {
right: 0;
}
.apiDrawerTab.apiDrawerTabOpen {
right: 25vw;
}
.apiDrawerCategoryButton {
@@ -464,34 +416,6 @@
@media (max-width: 900px) {
.apiDrawer {
width: 35vw;
right: -35vw;
}
.apiDrawer.apiDrawerOpen {
right: 0;
}
.apiDrawerTab.apiDrawerTabOpen {
right: 35vw;
}
.apiDrawerTab {
width: 35px;
height: 75px;
gap: 4px;
}
.apiDrawerTabIcon {
font-size: 12px;
}
.apiDrawerTabText {
font-size: 8px;
letter-spacing: 1px;
}
.apiDrawerTab:hover .apiDrawerTabText {
letter-spacing: 1.5px;
}
.apiDrawerHeader h3 {

View File

@@ -49,9 +49,6 @@ const StageWrapperPythonCPLT = () => {
`
);
// ★★★ 添加抽屉状态管理
const [apiDrawerOpen, setApiDrawerOpen] = useState(false);
// ★★★ 替换 workerRef用 Pool Ref
const poolRef = useRef(null); // 用这个存 Worker Pool
// const workerRef = useRef(null); // <-- 删掉
@@ -60,6 +57,27 @@ const StageWrapperPythonCPLT = () => {
const [errorModalVisible, setErrorModalVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [overlayActive, setOverlayActive] = useState(false);
const [apiSelectedCategory, setApiSelectedCategory] = useState('player');
const [isApiPanelReady, setIsApiPanelReady] = useState(() => window.iscreate === 1);
// Unity 加载完成前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]);
// CodeMirror编辑器引用
const editorRef = useRef(null);
@@ -650,14 +668,64 @@ const StageWrapperPythonCPLT = () => {
return parts;
}
function splitOr(stmt) {
if (!hasFuncCall(stmt)) {
return [stmt];
}
const parts = [];
let cur = '';
let skipChars = 0;
parse(stmt, (ch, depth, parenDepth, i, notInStr) => {
if (skipChars > 0) {
skipChars--;
return;
}
if (notInStr && depth === 0 && ch === 'o' && stmt[i + 1] === 'r') {
const prevChar = i > 0 ? stmt[i - 1] : '';
const nextChar = i + 2 < stmt.length ? stmt[i + 2] : '';
const isPrevWordChar = /[a-zA-Z0-9_]/.test(prevChar);
const isNextWordChar = /[a-zA-Z0-9_]/.test(nextChar);
if (!isPrevWordChar && !isNextWordChar) {
const t = cur.trim();
if (t) parts.push(t);
cur = '';
skipChars = 1;
return;
}
}
cur += ch;
});
const t = cur.trim();
if (t) parts.push(t);
return parts;
}
let count = 0;
for (const stmt of splitStatements(s)) {
const assign = findAssign(stmt);
if (!assign) {
count += splitComma(stmt).length;
let stmtCount = 0;
for (const orPart of splitOr(stmt)) {
stmtCount += splitComma(orPart).length;
}
count += stmtCount;
} else {
const lhs = stmt.slice(0, assign.index).trim();
count += hasComma(lhs) ? 1 : Math.max(1, splitComma(stmt.slice(assign.index + assign.length)).length);
if (hasComma(lhs)) {
count += 1;
} else {
const rhs = stmt.slice(assign.index + assign.length);
let rhsCount = 0;
for (const orPart of splitOr(rhs)) {
rhsCount += splitComma(orPart).length;
}
count += Math.max(1, rhsCount);
}
}
}
return count;
@@ -1168,22 +1236,15 @@ sys.settrace(trace_func)`;
);
};
// API参考抽屉组件
// API参考面板
const ApiDrawer = () => {
const [selectedCategory, setSelectedCategory] = useState('player');
const selectedCategory = apiSelectedCategory;
const setSelectedCategory = setApiSelectedCategory;
return (
<div className={`${styles.apiDrawer} ${apiDrawerOpen ? styles.apiDrawerOpen : ''}`}>
{/* 抽屉头部 */}
<div className={styles.apiDrawer}>
<div className={styles.apiDrawerHeader}>
<h3>🐍 Python API参考</h3>
<button
className={styles.apiDrawerClose}
onClick={() => setApiDrawerOpen(false)}
title="关闭API参考"
>
×
</button>
</div>
{/* 抽屉内容 */}
@@ -1226,6 +1287,13 @@ sys.settrace(trace_func)`;
))}
</div>
</div>
<div
className={`${styles.apiDrawerLoading}${isApiPanelReady ? ` ${styles.apiDrawerLoadingHidden}` : ''}`}
aria-hidden={isApiPanelReady}
>
<div className={styles.apiDrawerLoadingSpinner}></div>
<div className={styles.apiDrawerLoadingText}>正在加载中完成后即可插入代码</div>
</div>
</div>
);
};
@@ -1238,12 +1306,22 @@ sys.settrace(trace_func)`;
const { state } = view;
const { selection } = state;
const cursor = selection.main.head;
const withSafeNewlines = (text) => {
let next = text;
if (cursor > 0 && view.state.doc.sliceString(cursor - 1, cursor) !== '\n') {
next = '\n' + next;
}
if (!next.endsWith('\n')) {
next += '\n';
}
return next;
};
// 检查代码是否包含snippet占位符
if (code.includes('#{')) {
// 使用snippet功能插入代码
try {
const snippetTemplate = snippet(code);
const snippetTemplate = snippet(withSafeNewlines(code));
const completion = snippetTemplate(view, {
from: cursor,
to: cursor
@@ -1253,7 +1331,7 @@ sys.settrace(trace_func)`;
view.dispatch(completion);
} else {
// 如果snippet创建失败使用简化的插入方式
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
view.dispatch({
changes: {
from: cursor,
@@ -1265,7 +1343,7 @@ sys.settrace(trace_func)`;
} catch (error) {
console.warn('Snippet insertion failed, falling back to plain text:', error);
// 回退到普通插入移除snippet标记
const cleanCode = code.replace(/#{([^}]*)}/g, '$1');
const cleanCode = withSafeNewlines(code.replace(/#{([^}]*)}/g, '$1'));
view.dispatch({
changes: {
from: cursor,
@@ -1275,22 +1353,19 @@ sys.settrace(trace_func)`;
});
}
} else {
// 普通代码插入
const insertText = withSafeNewlines(code);
view.dispatch({
changes: {
from: cursor,
to: cursor,
insert: code
insert: insertText
},
selection: {
anchor: cursor + code.length
anchor: cursor + insertText.length
}
});
}
// 关闭API参考窗口
setApiDrawerOpen(false);
// 聚焦编辑器
view.focus();
}
@@ -1300,36 +1375,16 @@ sys.settrace(trace_func)`;
return (
<>
<ErrorModal />
<div style={{
position: 'relative',
height: '100%',
width: '100%',
overflow: 'hidden',
}}>
{/* API抽屉标签 */}
<div className={styles.editorWithApi}>
{ApiDrawer()}
<div
className={`${styles.apiDrawerTab} ${apiDrawerOpen ? styles.apiDrawerTabOpen : ''}`}
onClick={() => setApiDrawerOpen(!apiDrawerOpen)}
title={apiDrawerOpen ? "关闭API参考" : "打开API参考"}
className={styles.editorPane}
onWheel={(e) => e.stopPropagation()}
>
<div className={styles.apiDrawerTabIcon}>
{apiDrawerOpen ? '▶' : '◀'}
</div>
<div className={styles.apiDrawerTabText}>
{apiDrawerOpen ? '关闭' : 'API'}
</div>
</div>
{/* API抽屉 */}
<ApiDrawer />
{/* Python代码编辑器 - 现在占满整个高度 */}
<div onWheel={(e) => e.stopPropagation()} style={{ overflow: 'auto', position: 'relative', height: '100%' }}>
<CodeMirror
ref={editorRef}
style={{
width: '54vw',
// height: '92vh',
width: '100%',
height: '100%',
position: 'relative',
backgroundColor: '#010101',

View File

@@ -11,8 +11,11 @@ import RankBadgeGenerator from '../../playground/rank-badge-generator.js';
// const friendToggle = new FriendToggle();
window.RankBadgeGenerator = RankBadgeGenerator;
window.CONFIG = CONFIG;
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from '../../playground/devtool-control.js';
import { getUnityPlayerConfig, loadUnityLoaderScript } from '../../lib/unity-build-loader.js';
import { debug } from 'scratch-vm/src/util/log.js';
import GameLoadingOverlay from '../game-loading-overlay/game-loading-overlay.jsx';
import useGameLoadProgress from '../game-loading-overlay/use-game-load-progress.js';
const MAX_LEVELS = 500; // 最大关卡数据数量
const STORAGE_KEY = 'player_levels_data'; // 使用命名空间,防止键名冲突
@@ -246,6 +249,16 @@ const UnityComponent = function (props) {
const [getBlockNum, setBlockNum] = useState(0);
const [testDal, setTestDal] = useState(-1);
const canvasRef = useRef(null);
const {
progress: gameLoadProgress,
visible: gameLoadVisible,
onUnityProgress,
onUnityInstanceCreated
} = useGameLoadProgress();
const onUnityProgressRef = useRef(onUnityProgress);
const onUnityInstanceCreatedRef = useRef(onUnityInstanceCreated);
onUnityProgressRef.current = onUnityProgress;
onUnityInstanceCreatedRef.current = onUnityInstanceCreated;
const [isGameOverVisible, setIsGameOverVisible] = useState(false);
const [isGameOkVisible, setIsGameOkVisible] = useState(false);
const [isLockNextButton, setIsLockNextButton] = useState(true);
@@ -741,7 +754,7 @@ const UnityComponent = function (props) {
useEffect(() => {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// 临时禁用 onbeforeunload 事件监听,以防止弹出确认离开弹窗
@@ -1520,8 +1533,9 @@ const UnityComponent = function (props) {
window.iscreate = 0;
const boot = () => {
createUnityInstance(canvas, config, (progress) => {
// console.log(`加载进度: ${progress * 100}%`);
onUnityProgressRef.current(progress);
}).then((unityInstance) => {
onUnityInstanceCreatedRef.current();
window.unityInstance = unityInstance;
window.waitForUnityStepFinish = waitForUnityStepFinish;
window.waitForUnityVehicleStepFinish = waitForUnityVehicleStepFinish;
@@ -1585,6 +1599,7 @@ const UnityComponent = function (props) {
}}
>
<canvas id="unity-canvas" ref={canvasRef} width="960" height="600" style={{ width: '100%', height: '100%' }}></canvas>
<GameLoadingOverlay visible={gameLoadVisible} progress={gameLoadProgress} />
<section
className="u-align-center u-black u-clearfix u-container-align-center u-container-style u-dialog-block u-opacity u-opacity-50 u-valign-middle-md u-valign-middle-xl u-dialog-section-8"

View File

@@ -0,0 +1,119 @@
import CONFIG from '../playground/config.js';
const FILES = {
dataUrl: 'mstest5.data.br',
frameworkUrl: 'mstest5.framework.js.br',
codeUrl: 'mstest5.wasm.br'
};
// 压缩后 wasm 约 4.4MB;用户侧曾出现只下到 ~800KB 的截断包
const MIN_BYTES = {
dataUrl: 1024 * 1024,
frameworkUrl: 20 * 1024,
codeUrl: 2 * 1024 * 1024
};
const WASM_MAGIC = [0x00, 0x61, 0x73, 0x6d];
function withVersion(url) {
const version = CONFIG.version || Date.now();
return `${url}${url.includes('?') ? '&' : '?'}v=${encodeURIComponent(version)}`;
}
function buildCandidates() {
const local = '/unity/Build';
const cdn = CONFIG.unitycdnbuilddir;
if (CONFIG.usecdn) {
return [cdn, local].filter(Boolean);
}
return [local, cdn].filter(Boolean);
}
function isUncompressedWasm(buffer) {
const bytes = new Uint8Array(buffer);
return WASM_MAGIC.every((value, index) => bytes[index] === value);
}
function looksLikeJavascript(buffer) {
const head = new TextDecoder().decode(buffer.slice(0, 80));
return /function|unityFramework|^\s*var |^\s*const |^\s*!function/.test(head);
}
function looksLikeUnityData(buffer) {
const head = new TextDecoder().decode(buffer.slice(0, 16));
return head.startsWith('UnityWebData1.0');
}
function toTrustedUrl(fileName, buffer, sourceUrl) {
if (fileName.endsWith('.wasm.br') && isUncompressedWasm(buffer)) {
return URL.createObjectURL(new Blob([buffer], { type: 'application/wasm' }));
}
if (fileName.endsWith('.js.br') && looksLikeJavascript(buffer)) {
return URL.createObjectURL(new Blob([buffer], { type: 'application/javascript' }));
}
if (fileName.endsWith('.data.br') && looksLikeUnityData(buffer)) {
return URL.createObjectURL(new Blob([buffer]));
}
return sourceUrl;
}
async function fetchCompleteAsset(fileName, minBytes) {
let lastError = null;
for (const base of buildCandidates()) {
const url = withVersion(`${base}/${fileName}`);
try {
const response = await fetch(url, { cache: 'no-store' });
if (!response.ok) {
lastError = new Error(`${url} HTTP ${response.status}`);
continue;
}
const buffer = await response.arrayBuffer();
if (buffer.byteLength < minBytes) {
console.warn(`[Unity] ${fileName} 被截断: ${buffer.byteLength} bytes来自 ${url}),期望至少 ${minBytes}`);
lastError = new Error(`${fileName} truncated: ${buffer.byteLength}`);
continue;
}
console.log(`[Unity] ${fileName} 下载完整: ${buffer.byteLength} bytes from ${url}`);
return toTrustedUrl(fileName, buffer, url);
} catch (error) {
console.warn(`[Unity] 下载失败 ${url}`, error);
lastError = error;
}
}
throw lastError || new Error(`无法完整下载 ${fileName}`);
}
export async function getUnityPlayerConfig() {
const [dataUrl, frameworkUrl, codeUrl] = await Promise.all([
fetchCompleteAsset(FILES.dataUrl, MIN_BYTES.dataUrl),
fetchCompleteAsset(FILES.frameworkUrl, MIN_BYTES.frameworkUrl),
fetchCompleteAsset(FILES.codeUrl, MIN_BYTES.codeUrl)
]);
const streamingAssetsUrl = (CONFIG.usecdn && CONFIG.unitycdndir)
? `${CONFIG.unitycdndir}/StreamingAssets`
: '/unity/StreamingAssets';
return {
dataUrl,
frameworkUrl,
codeUrl,
streamingAssetsUrl,
companyName: 'DefaultCompany',
productName: 'Web A',
productVersion: String(CONFIG.version || '1.0')
};
}
export function loadUnityLoaderScript() {
if (typeof window.createUnityInstance === 'function') {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = withVersion('/unity/Build/mstest5.loader.js');
script.onload = () => resolve();
script.onerror = () => reject(new Error('Unity loader 加载失败'));
document.body.appendChild(script);
});
}

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>关于我们 - 灵丌编程001CODE - 你身边的青少年编程学习平台 - 图形化、Python、C++</title>
<script src="js/nav-responsive.js?v=<%= htmlWebpackPlugin.options.version %>" defer></script>
<style>
* {
box-sizing: border-box;
@@ -111,6 +112,138 @@
list-style: none;
}
.nav-toggle {
display: none;
width: 40px;
height: 40px;
margin-left: auto;
padding: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #f8fafc;
cursor: pointer;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
flex-shrink: 0;
appearance: none;
-webkit-appearance: none;
}
.nav-toggle:focus-visible {
outline: 2px solid rgba(79, 140, 255, 0.55);
outline-offset: 2px;
}
.nav-toggle span {
display: block;
width: 18px;
height: 2px;
background: #1e1a4c;
border-radius: 1px;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-open .nav-toggle span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav-open .nav-toggle span:nth-child(2) {
opacity: 0;
}
.nav-open .nav-toggle span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
.nav-backdrop {
display: none;
}
@media (max-width: 960px) {
body.nav-open {
overflow: hidden;
}
.nav-inner {
gap: 12px;
padding: 4px 12px;
}
.brand {
padding: 4px 0;
}
.brand-logo {
width: 156px;
height: 44px;
}
.nav-toggle {
display: inline-flex;
}
.nav-list {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
flex-direction: column;
align-items: stretch;
gap: 4px;
max-height: calc(100vh - 64px);
overflow-y: auto;
padding: 12px 16px 20px;
background: #ffffff;
border-top: 1px solid #e2e8f0;
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
z-index: 51;
}
.nav-open .nav-list {
display: flex;
}
.nav-backdrop {
position: fixed;
top: 64px;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.32);
z-index: 49;
}
.nav-open .nav-backdrop {
display: block;
}
.nav-list li {
width: 100%;
}
.nav-list .nav-item,
.nav-list .nav__pillAI {
display: flex;
width: 100%;
min-height: 44px;
padding: 10px 12px;
border-radius: 10px;
align-items: center;
box-sizing: border-box;
}
.nav-list .nav-item:hover {
background: #f8fafc;
}
.nav-list .nav__pillAI:hover {
transform: none;
}
}
.nav-item {
display: flex;
align-items: center;
@@ -130,7 +263,19 @@
}
.nav-item.about {
color: #0790d9;
color: #1e1a4c;
}
.nav-item--active,
.nav-item[aria-current="page"] {
display: inline-flex;
align-items: center;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
color: #4F8CFF;
box-sizing: border-box;
}
.nav__pill {
@@ -169,6 +314,87 @@
color: #ffffff;
}
/* 灵丌AI 导航胶囊 */
.nav__pillAI {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav__pillAI:hover {
transform: scale(1.05);
}
.nav__pillAI:focus {
outline: none;
}
.nav__pillAI:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav__pillAIText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 600;
font-size: 14px;
line-height: 1.5714285714;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIText {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav__pillAIIcon {
width: 18px;
height: 18px;
display: block;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIIcon {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
main {
position: relative;
height: 1660px;
@@ -953,16 +1179,24 @@
<a class="brand" href="/home.html" aria-label="001CODE">
<img class="brand-logo" src="../images/logo.svg" alt="001CODE" />
</a>
<button type="button" class="nav-toggle" aria-label="打开菜单" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
<ul class="nav-list" aria-label="主导航">
<li><a class="nav-item home" href="/home.html">主页</a></li>
<li>
<a class="nav__pill" href="/">
<span class="nav__pillText">学习入口</span>
<span class="nav__pillNew">new</span>
<a class="nav-item" href="/welcome.html">灵丌编程</a>
</li>
<li>
<a class="nav__pillAI" href="/ai.html">
<img class="nav__pillAIIcon" src="../images/ai-sparkle.svg" alt="" />
<span class="nav__pillAIText">灵丌AI</span>
</a>
</li>
<li><a class="nav-item event" href="/competition_list.html">赛事详情</a></li>
<li><a class="nav-item about" href="/aboutus.html">关于我们</a></li>
<li><a class="nav-item about nav-item--active" href="/aboutus.html" aria-current="page">关于我们</a></li>
</ul>
</div>
</header>
@@ -1091,7 +1325,7 @@
<h2 class="cta-title">灵丌编程001CODE城市合伙人招募中</h2>
</div>
<div class="cta-grid">
<a href="mailto:contact@001Code.com" class="contact-card">
<a href="mailto:support@shxshf.com" class="contact-card">
<div class="icon-box">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="4" width="20" height="16" rx="2"></rect>
@@ -1100,11 +1334,11 @@
</div>
<div class="card-info">
<span class="card-label">邮箱地址</span>
<span class="card-value">wangshenxing@shxshf.com</span>
<span class="card-value">support@shxshf.com</span>
</div>
</a>
<button class="contact-card" onclick="navigator.clipboard.writeText('17380126742')">
<button class="contact-card" onclick="navigator.clipboard.writeText('400-659-1659')">
<div class="icon-box">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@@ -1114,7 +1348,7 @@
</div>
<div class="card-info">
<span class="card-label">咨询热线</span>
<span class="card-value">135-2066-2111</span>
<span class="card-value">400-659-1659</span>
</div>
</button>
</div>
@@ -1123,8 +1357,8 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -0,0 +1,4 @@
// 给 chunks: [] 的 AI 静态页注入与 login 相同的 config.js
import CONFIG from './config.js';
window.CONFIG = CONFIG;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
// ai.html 的 CONFIG 由 head 中的 aiConfig chunk 注入(与 login 同一份 config.js
if (typeof window.__AI_PAGE_BOOTED__ === 'undefined') {
document.addEventListener('DOMContentLoaded', function () {
if (!window.__AI_PAGE_BOOTED__) {
console.warn('[ai.js] 内联 boot 未执行,请检查 ai.html 是否为最新构建');
}
});
}

View File

@@ -6,6 +6,9 @@ let timerId = localStorage.getItem('loginStatusCheckTimerId');
async function checkLoginStatus() {
const token = localStorage.getItem('access_token');
if (!token) {
try {
localStorage.setItem('returnUrl', window.location.pathname + window.location.search);
} catch (e) {}
alert('您尚未登录或登录已过期,请重新登录');
window.location.href = window.location.origin + '/login.html';
return;

View File

@@ -0,0 +1,360 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>即将上线 - 灵丌AI - 001CODE</title>
<style>
:root {
--bg: #f0f4f9;
--ink: #0f172a;
--ai-cyan: #00CCFF;
--ai-accent: #00CCFF;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
background: #0f172a;
color: #fff;
font-family: "PingFang SC", "Noto Sans SC", system-ui, -apple-system, sans-serif;
overflow: hidden;
display: flex; align-items: center; justify-content: center;
}
/* ===== 开幕动画层 ===== */
.reveal-overlay {
position: fixed; inset: 0; z-index: 100;
background: radial-gradient(circle at center, rgba(0,3,255,0.15) 0%, #0f172a 70%);
display: flex; align-items: center; justify-content: center;
animation: overlayFade 0.8s 0.6s cubic-bezier(0.4,0,0.2,1) forwards;
}
@keyframes overlayFade {
0% { opacity: 1; }
100% { opacity: 0; pointer-events: none; }
}
/* 中心光圈扩散 */
.reveal-ring {
position: absolute; width: 20px; height: 20px; border-radius: 50%;
border: 1px solid rgba(0,204,255,0.6);
animation: ringExpand 1.2s 0.1s cubic-bezier(0.25,0.8,0.25,1) forwards;
}
.reveal-ring:nth-child(2) { animation-delay: 0.25s; border-color: rgba(0,3,255,0.4); }
.reveal-ring:nth-child(3) { animation-delay: 0.4s; border-color: rgba(0,3,255,0.3); }
@keyframes ringExpand {
0% { width: 20px; height: 20px; opacity: 1; }
100% { width: 800px; height: 800px; opacity: 0; }
}
/* 中心粒子 */
.reveal-particle {
position: absolute; width: 4px; height: 4px; border-radius: 50%;
background: #00CCFF; opacity: 0;
animation: particleBurst 0.9s 0.15s ease-out forwards;
}
@keyframes particleBurst {
0% { opacity: 1; transform: translate(0, 0) scale(1); }
100% { opacity: 0; transform: translate(var(--tx), var(--ty)) scale(0); }
}
/* 中心 Logo 缩放出现 */
.reveal-logo {
position: relative; z-index: 2; text-align: center;
animation: logoScaleIn 0.7s 0.2s cubic-bezier(0.34,1.56,0.64,1) both;
}
@keyframes logoScaleIn {
0% { transform: scale(0); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.reveal-logo__icon { font-size: 72px; margin-bottom: 16px; filter: drop-shadow(0 0 30px rgba(0,204,255,0.5)); }
.reveal-logo__text {
font-size: 32px; font-weight: 700; letter-spacing: 2px;
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
/* ===== 主页面 ===== */
.page {
width: 100%; max-width: 800px; padding: 48px 32px;
text-align: center; z-index: 1;
animation: contentFadeIn 0.8s 1s ease both;
}
@keyframes contentFadeIn {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
.badge {
display: inline-flex; align-items: center; gap: 8px;
padding: 6px 18px; border-radius: 50px;
background: rgba(0,3,255,0.12); border: 1px solid rgba(0,3,255,0.2);
color: #00CCFF; font-size: 13px; font-weight: 600; margin-bottom: 28px;
}
.badge__dot {
width: 8px; height: 8px; border-radius: 50%; background: #00CCFF;
animation: dotPulse 1.5s ease-in-out infinite;
}
@keyframes dotPulse {
0%, 100% { opacity: 1; box-shadow: 0 0 6px #00CCFF; }
50% { opacity: 0.4; box-shadow: 0 0 12px #00CCFF; }
}
h1 {
font-size: 42px; font-weight: 700; margin: 0 0 16px; color: #fff;
letter-spacing: 1px;
}
h1 span {
background: linear-gradient(90deg, rgba(0, 204, 255, 1) 0%, rgba(0, 3, 255, 1) 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.subtitle { font-size: 17px; color: #94a3b8; margin: 0 0 48px; line-height: 1.7; }
/* ===== 骨架屏卡片 ===== */
.skeleton-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 70px; position: relative; }
.skeleton-card {
background: rgba(255,255,255,0.04); border-radius: 14px;
padding: 24px 20px; border: 1px solid rgba(255,255,255,0.06);
overflow: hidden; position: relative;
transition: opacity 0.5s ease, transform 0.5s ease;
}
.skeleton-card.fade-out {
opacity: 0; transform: scale(0.95); pointer-events: none;
}
.skeleton-card::after {
content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.03), transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { left: -100%; }
100% { left: 100%; }
}
.sk-circle {
width: 40px; height: 40px; border-radius: 10px; margin: 0 auto 16px;
background: rgba(0,204,255,0.1);
}
.sk-line { height: 12px; border-radius: 6px; margin: 0 auto 8px; background: rgba(255,255,255,0.06); }
.sk-line--lg { width: 80%; }
.sk-line--md { width: 60%; }
.sk-line--sm { width: 40%; }
/* ===== 真实功能卡片 ===== */
.feature-grid {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
position: absolute; top: 0; left: 0; width: 100%;
pointer-events: none; opacity: 0;
transition: opacity 0.6s 0.3s ease;
}
.feature-grid.show { opacity: 1; pointer-events: auto; }
.feature-card {
background: rgba(255,255,255,0.04); border-radius: 14px;
padding: 28px 20px; border: 1px solid rgba(255,255,255,0.06);
text-align: center; transition: all 0.35s ease;
animation: cardReveal 0.6s ease both;
}
.feature-card:nth-child(1) { animation-delay: 0.1s; }
.feature-card:nth-child(2) { animation-delay: 0.25s; }
.feature-card:nth-child(3) { animation-delay: 0.4s; }
@keyframes cardReveal {
0% { opacity: 0; transform: translateY(16px) scale(0.95); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
.feature-card:hover {
border-color: rgba(0,204,255,0.3); background: rgba(0,204,255,0.06);
transform: translateY(-3px);
box-shadow: 0 8px 30px rgba(0,3,255,0.12);
}
.feature-card__icon {
width: 80px; height: 80px; border-radius: 14px; margin: 0 auto 16px;
display: flex; align-items: center; justify-content: center; font-size: 26px;
}
.feature-card__icon--green { background: rgba(34,197,94,0.12); }
.feature-card__icon--blue { background: rgba(59,130,246,0.12); }
.feature-card h3 {
font-size: 18px; font-weight: 700; margin: 0 0 8px; color: #fff;
}
.feature-card p {
font-size: 13px; color: #94a3b8; margin: 0 0 16px; line-height: 1.6;
}
.feature-card__link {
display: inline-flex; align-items: center; gap: 5px;
padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600;
text-decoration: none; transition: all 0.3s;
border: 1px solid rgba(0,204,255,0.25);
color: #00CCFF; background: transparent;
}
.feature-card__link:hover {
background: rgba(0,204,255,0.15); border-color: #00CCFF;
}
/* ===== 通知订阅区 ===== */
.notify-box {
display: inline-flex; align-items: center; gap: 10px;
padding: 14px 28px; border-radius: 50px;
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08);
transition: all 0.3s ease;
}
.notify-box:hover { border-color: rgba(0,204,255,0.3); background: rgba(0,204,255,0.05); }
.notify-box__icon { font-size: 18px; }
.notify-box__text { font-size: 14px; color: #94a3b8; }
.notify-box__text strong { color: #00CCFF; font-weight: 600; }
.back-link {
display: inline-flex; align-items: center; gap: 8px;
margin-top: 36px; color: #00CCFF; text-decoration: none;
font-size: 14px; font-weight: 600; transition: all 0.3s;
padding: 10px 28px; border-radius: 30px;
border: 1px solid rgba(0,204,255,0.3);
background: rgba(0,204,255,0.08);
animation: backGlow 2s ease-in-out infinite;
}
@keyframes backGlow {
0%, 100% { box-shadow: 0 0 8px rgba(0,204,255,0.2); border-color: rgba(0,204,255,0.3); }
50% { box-shadow: 0 0 22px rgba(0,204,255,0.5); border-color: rgba(0,204,255,0.7); }
}
.back-link:hover {
background: rgba(0,204,255,0.18); border-color: #00CCFF;
box-shadow: 0 0 30px rgba(0,204,255,0.6);
animation: none;
}
/* ===== 二维码区 ===== */
.qr-section { margin-top: 28px; }
.qr-section img {
width: 130px; height: 130px; border-radius: 12px;
border: 2px solid rgba(255,255,255,0.08);
transition: all 0.3s ease;
}
.qr-section img:hover { border-color: rgba(0,204,255,0.4); box-shadow: 0 0 24px rgba(0,204,255,0.2); }
.qr-section p { font-size: 12px; color: #64748b; margin-top: 8px; }
@media (max-width: 640px) {
.skeleton-grid { grid-template-columns: 1fr; }
h1 { font-size: 28px; }
.reveal-logo__icon { font-size: 48px; }
}
</style>
</head>
<body>
<!-- ===== 开幕动画层 ===== -->
<div class="reveal-overlay">
<div class="reveal-ring"></div>
<div class="reveal-ring"></div>
<div class="reveal-ring"></div>
<div class="reveal-particle" style="--tx:60px;--ty:-80px;"></div>
<div class="reveal-particle" style="--tx:-70px;--ty:-50px;"></div>
<div class="reveal-particle" style="--tx:80px;--ty:40px;"></div>
<div class="reveal-particle" style="--tx:-50px;--ty:70px;"></div>
<div class="reveal-particle" style="--tx:20px;--ty:-100px;"></div>
<div class="reveal-particle" style="--tx:-90px;--ty:20px;"></div>
<div class="reveal-logo">
<div class="reveal-logo__icon">✨</div>
<div class="reveal-logo__text">灵丌AI</div>
</div>
</div>
<!-- ===== 主内容 ===== -->
<div class="page">
<div class="badge">
<span class="badge__dot"></span>
COMING SOON
</div>
<h1>灵丌AI<span> 即将上线</span></h1>
<p class="subtitle">贯穿青少年科创教育始终全新的AI学习工具即将精彩呈现敬请期待。</p>
<!-- 骨架屏 + 真实卡片容器 -->
<div class="skeleton-grid" id="cardContainer" style="min-height: 220px;">
<!-- 骨架占位 -->
<div class="skeleton-card" id="sk1">
<div class="sk-circle"></div>
<div class="sk-line sk-line--lg"></div>
<div class="sk-line sk-line--md"></div>
<div class="sk-line sk-line--sm"></div>
</div>
<div class="skeleton-card" id="sk2">
<div class="sk-circle"></div>
<div class="sk-line sk-line--lg"></div>
<div class="sk-line sk-line--md"></div>
<div class="sk-line sk-line--sm"></div>
</div>
<div class="skeleton-card" id="sk3">
<div class="sk-circle"></div>
<div class="sk-line sk-line--lg"></div>
<div class="sk-line sk-line--md"></div>
<div class="sk-line sk-line--sm"></div>
</div>
<!-- 真实卡片 -->
<div class="feature-grid" id="featureGrid">
<div class="feature-card">
<div class="feature-card__icon">
<img src="../images/aigc/icon3.png" alt="AI创意课程" style="width: 100%; height: 100%;" />
</div>
<h3>AI创意课程体系</h3>
<p>采用全新创意式项目制学习及评价体系赋能青少年AI素养培养。</p>
</div>
<div class="feature-card">
<div class="feature-card__icon">
<img src="../images/aigc/icon2.png" alt="AI实践平台" style="width: 100%; height: 100%;" />
</div>
<h3>AI实践平台</h3>
<p>一站式满足青少年AI素养学习需求多模态工具助力探究式AI学习。</p>
</div>
<div class="feature-card">
<div class="feature-card__icon">
<img src="../images/aigc/iocn1.png" alt="赛事体系" style="width: 100%; height: 100%;" />
</div>
<h3>赛事体系</h3>
<p>每一级都有出口,每一步都有回响。</p>
</div>
</div>
</div>
<!-- 二维码 -->
<div class="qr-section">
<img src="../images/qrcode.jpg" alt="公众号二维码" />
<p>扫码关注“灵丌编程”,了解更多资讯</p>
</div>
<a href="/home.html" class="back-link">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
返回首页
</a>
</div>
<script>
// 骨架屏 → 真实内容 过渡
(function() {
var delay = 2500; // 2.5秒后切换
function revealContent() {
var sk1 = document.getElementById('sk1');
var sk2 = document.getElementById('sk2');
var sk3 = document.getElementById('sk3');
var grid = document.getElementById('featureGrid');
// 骨架渐隐
if (sk1) sk1.classList.add('fade-out');
if (sk2) { sk2.classList.add('fade-out'); sk2.style.transitionDelay = '0.1s'; }
if (sk3) { sk3.classList.add('fade-out'); sk3.style.transitionDelay = '0.2s'; }
// 延迟展示真实卡片
setTimeout(function() {
if (grid) grid.classList.add('show');
// 隐藏骨架 DOM
setTimeout(function() {
if (sk1) sk1.style.display = 'none';
if (sk2) sk2.style.display = 'none';
if (sk3) sk3.style.display = 'none';
}, 500);
}, 300);
}
// 开幕动画结束后开始计时
setTimeout(revealContent, delay);
})();
</script>
</body>
</html>

View File

@@ -87,8 +87,8 @@
<footer>
<div class="footer-line"></div>
<p class="footer-text">ICP备案号<a style="color:#478ac9 ;" href=" " target="_blank">蜀ICP备2022002563号-5</a>
公安备案号:<a style="color:#478ac9 ;" href="https://beian.mps.gov.cn/" target="_blank">川公网安备51011202000980号</a>
<p class="footer-text">ICP备案号<a style="color:#478ac9 ;" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p>
</footer>
</div>

View File

@@ -1,6 +1,6 @@
// 导入配置信息
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
// 获取 URL 参数的函数
@@ -166,7 +166,7 @@ function showPlatformGuide() {
document.addEventListener('DOMContentLoaded', function () {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
// window.location.href = localStorage.getItem('returnUrl');

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>赛事详情 - 灵丌编程001CODE - 你身边的青少年编程学习平台 - 图形化、Python、C++</title>
<script src="js/nav-responsive.js?v=<%= htmlWebpackPlugin.options.version %>" defer></script>
</head>
<style>
:root {
@@ -123,6 +124,131 @@
gap: 32px;
}
.nav-toggle {
display: none;
width: 40px;
height: 40px;
margin-left: auto;
padding: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #f8fafc;
cursor: pointer;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
flex-shrink: 0;
appearance: none;
-webkit-appearance: none;
}
.nav-toggle:focus-visible {
outline: 2px solid rgba(79, 140, 255, 0.55);
outline-offset: 2px;
}
.nav-toggle span {
display: block;
width: 18px;
height: 2px;
background: #1e1a4c;
border-radius: 1px;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-open .nav-toggle span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav-open .nav-toggle span:nth-child(2) {
opacity: 0;
}
.nav-open .nav-toggle span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
.nav-backdrop {
display: none;
}
@media (max-width: 960px) {
body.nav-open {
overflow: hidden;
}
.nav-inner {
gap: 12px;
padding: 4px 12px;
}
.nav-logo {
position: static;
width: 156px;
height: 44px;
}
.nav-toggle {
display: inline-flex;
}
.nav-list {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
flex-direction: column;
align-items: stretch;
gap: 4px;
max-height: calc(100vh - 64px);
overflow-y: auto;
padding: 12px 16px 20px;
background: #ffffff;
border-top: 1px solid #e2e8f0;
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
z-index: 51;
}
.nav-open .nav-list {
display: flex;
}
.nav-backdrop {
position: fixed;
top: 64px;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.32);
z-index: 49;
}
.nav-open .nav-backdrop {
display: block;
}
.nav-list .nav-link,
.nav-list .nav__pillAI {
display: flex;
width: 100%;
min-height: 44px;
padding: 10px 12px;
border-radius: 10px;
align-items: center;
box-sizing: border-box;
}
.nav-list .nav-link:hover {
background: #f8fafc;
}
.nav-list .nav__pillAI:hover {
transform: none;
}
}
.nav-link {
text-decoration: none;
color: #020f30;
@@ -138,7 +264,14 @@
}
.nav-link-current {
color: #0790d9;
display: inline-flex;
align-items: center;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
color: #4F8CFF;
box-sizing: border-box;
}
.nav__pill {
@@ -177,6 +310,86 @@
color: #ffffff;
}
/* 灵丌AI 导航胶囊 */
.nav__pillAI {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav__pillAI:hover {
transform: scale(1.05);
}
.nav__pillAI:focus {
outline: none;
}
.nav__pillAI:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav__pillAIText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 600;
font-size: 14px;
line-height: 1.5714285714;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIText {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav__pillAIIcon {
width: 18px;
height: 18px;
display: block;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIIcon {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
.hero {
@@ -345,10 +558,10 @@
}
.card-cover {
width: 400px;
width: 300px;
height: 230px;
display: block;
object-fit: cover;
object-fit: contain;
border-radius: 16px;
/* 所有四个角都是 16px 的圆角 */
}
@@ -630,13 +843,19 @@
<a class="brand" href="/home.html" aria-label="001CODE">
<img class="nav-logo" src="../images/logo.svg" alt="001CODE" />
</a>
<button type="button" class="nav-toggle" aria-label="打开菜单" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
<nav class="nav-list" aria-label="主导航">
<a class="nav-link nav-link-home" href="/home.html">主页</a>
<a class="nav__pill" href="/">
<span class="nav__pillText">学习入口</span>
<span class="nav__pillNew">new</span>
<a class="nav-link" href="/welcome.html">灵丌编程</a>
<a class="nav__pillAI" href="/ai.html">
<img class="nav__pillAIIcon" src="../images/ai-sparkle.svg" alt="" />
<span class="nav__pillAIText">灵丌AI</span>
</a>
<a class="nav-link nav-link-current" href="/competition_list.html">赛事详情</a>
<a class="nav-link nav-link-current" href="/competition_list.html" aria-current="page">赛事详情</a>
<a class="nav-link" href="/aboutus.html">关于我们</a>
</nav>
</div>
@@ -683,7 +902,7 @@
</div>
</div>
<section class="cta" aria-label="学习入口">
<section class="cta" aria-label="灵丌编程">
<div class="cta-bg"></div>
<div class="cta-inner">
<img class="cta-mask" src="../images/competition-cta-mask.svg" alt="" />
@@ -697,8 +916,8 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -79,7 +79,7 @@ const renderCompetitions = (competitions, currentServerTime) => {
const endTimeObj = new Date(endTime * 1000);
const timeStr = `${formatDate(startTimeObj)}-${formatDate(endTimeObj)}`;
const coverSrc = item.cover_image_url || '../images/image-119.png';
const coverSrc = item.cover_image_url || '../images/default_compertion.png';
/*
If item.is_competition is true, prefix the URL with https://coding.scxqn.com/

View File

@@ -159,8 +159,8 @@
<!-- Footer -->
<footer>
<div class="footer-line"></div>
<p class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<p class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p>
</footer>
</div>

View File

@@ -1,11 +1,11 @@
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
let gamelvdata = {};
document.addEventListener('DOMContentLoaded', function () {
if (CONFIG.disabledev) {
disableDevtool({
initDisableDevtool({
ondevtoolopen: () => {
},
interval: 500,

View File

@@ -1,12 +1,12 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
@@ -14,7 +14,7 @@
// // 可以根据需要添加其他配置
// disabledev: false,
// disabledev: true,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
@@ -78,12 +78,12 @@
// const CONFIG = {
// // DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://test-service.001code.cn",
// // DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://service.001code.com",
// DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// // DataServerBaseUrl: "https://service.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
@@ -91,8 +91,8 @@
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// // disabledev: true,
// disabledev: true,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
@@ -157,15 +157,14 @@
const CONFIG = {
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test-service.001code.cn", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test-service.001code.cn",
DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://test-service.001code.cn",
// DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://service.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://service.001code.com",
DataServerBaseUrl: "https://service.001code.com",
// DataServerBaseUrl: "https://service.001code.com",
// DataServerBaseUrl: "https://service.001code.com",
version: "2026071701",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_2026071701',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
@@ -177,12 +176,14 @@ const CONFIG = {
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_2026071701',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_2026071701/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
/** AI 课程课件 HTML 资源(原 static/html 已迁到 OSS */
aiCourseOssBase: 'https://oss.001code.cn/ai_courses',
// 可以根据需要添加其他配置
disabledev: false,
disabledev: true,
usecdn: false,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&

View File

@@ -1,288 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260717",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
<<<<<<< HEAD
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260717',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260717/Build',
=======
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260617/Build',
>>>>>>> b08562d603e88576042ff972dedf771c32224b4b
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -1,283 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260520",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260616',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260616',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260616/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -1,283 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260717",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260717',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260717/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

View File

@@ -1,283 +0,0 @@
// config.js
// const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025010301",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn :true, //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:150,
// SSN_COMPETITION_ID: [13],
// // 计算关卡实际id
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
// const CONFIG = {
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// // DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// // DataServerBaseUrl: "https://test.server.001code.com",
// version : "2025011001",
// unitycdndir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826',
// unitycdnbuilddir : 'https://oss-rpwqy.jjwlcdn.com/001_code_unity_res_20250826/Build',
// python_pyodide_cdn_dir : 'https://oss-rpwqy.jjwlcdn.com/001_code_python_res_20241213/pyodide/',
// // 可以根据需要添加其他配置
// // disabledev: false,
// disabledev: false,
// usecdn :true, //资源走CDN
// lvlock :false, //关卡5关解锁,下一关加锁
// lvlock_normal_learn : !(typeof localStorage !== 'undefined' &&
// (localStorage.getItem('player_id') === '32' || localStorage.getItem('player_id') === '1506'|| localStorage.getItem('player_id') === '24'|| localStorage.getItem('player_id') === '1550')), //常规关起锁
// lockupdate :false, //上传按钮
// mactchlv : false, //比赛关卡
// learnvideo : true,
// gameNumber:120,
// // 计算关卡实际id
// SSN_COMPETITION_ID: [21,22,23],
// getRealLvId(gamelv,gameid, ismatch, gameNumber){
// if(localStorage.getItem('platformMode') === 'competition'){
// return parseInt(window.unity_game_number, 10)
// }else{
// if (!ismatch){
// return ((gamelv-1)*20*gameNumber)+((gameid-1)*gameNumber)
// }else{
// //TODO 这里维护一张匹配关卡映射表
// if (gamelv === 240896 && gameid === 0) {
// return 10000;
// }
// }
// }
// },
// lastGameStyle: '',
// lastGameDefaultSkin: 0,
// checkGameStyle(realLvId){
// let gameStyle = '';
// let gameDefaultSkin = 0;
// if ((realLvId >= 1 && realLvId <= 400) || (realLvId >= 20001 && realLvId <= 30000)) {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// } else if ((realLvId >= 401 && realLvId <= 800) || (realLvId >= 10001 && realLvId <= 20000)) {
// gameStyle = 'chinese';
// gameDefaultSkin = 1;
// } else if ((realLvId >= 801 && realLvId <= 1200) || (realLvId >= 40001 && realLvId <= 50000)) {
// gameStyle = 'redArmy';
// gameDefaultSkin = 2;
// } else if ((realLvId >= 1201 && realLvId <= 1600) || (realLvId >= 30001 && realLvId <= 40000)) {
// gameStyle = 'numMan';
// gameDefaultSkin = 3;
// }else {
// gameStyle = 'default';
// gameDefaultSkin = 0;
// }
// if (gameStyle !== this.lastGameStyle) {
// window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
// this.lastGameStyle = gameStyle;
// }
// if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// setTimeout(() => {
// window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
// console.log("CallChangeSkin >>> ", gameDefaultSkin);
// }, 3000);
// this.lastGameDefaultSkin = gameDefaultSkin;
// }
// }
// };
const CONFIG = {
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com", // 在这里定义服务器地址
// DataServerBaseUrl: "https://test.server.001code.com",
DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
// DataServerBaseUrl: "https://test.server.001code.com",
version: "20260520",
/** OSS 真实地址(生产环境浏览器直连) */
unityCdnRemote: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
/** 方案 Bnginx / webpack 同源代理前缀(方案 A 直连 OSS 请保持 false */
unityCdnDevProxy: '/cdn-unity',
/** 本地 npm start 也走代理(仅 unityCdnUseSiteProxy 或此项为 true 时生效) */
unityCdnUseDevProxy: false,
/** 生产 nginx 反向代理 OSS 同路径时设为 true方案 B */
unityCdnUseSiteProxy: false,
unitycdndir: 'https://oss.eanic.cn/001_code_cocos_res_20260617',
unitycdnbuilddir: 'https://oss.eanic.cn/001_code_cocos_res_20260617/Build',
python_pyodide_cdn_dir: 'https://oss.eanic.cn/001_code_python_res_20241213/pyodide/',
// 可以根据需要添加其他配置
disabledev: false,
usecdn: true,
lvlock: false,
lvlock_normal_learn: !(typeof localStorage !== 'undefined' &&
(localStorage.getItem('player_id') === '37' || localStorage.getItem('player_id') === '45' || localStorage.getItem('player_id') === '55')),
lockupdate: false,
mactchlv: false,
learnvideo: true,
gameNumber: 120,
//计算关卡实际id
SSN_COMPETITION_ID: [13],
NOT_ALLOWED_COMPETITION_ID: [72, 62, 64, 65, 69, 61, 63, 67, 70, 74, 73, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
BEGINNING_REAL_LVID: 91600,
END_REAL_LVID: 93100,
SIMULATE_COMPETITION_ID: [149], // 用于测试的模拟比赛ID
HIDE_COMPETITION_TIME_ID: [154, 155, 156], // 需要隐藏比赛时间的比赛ID列表
// 计算关卡实际id
getRealLvId(gamelv, gameid, ismatch, gameNumber) {
if (localStorage.getItem('platformMode') === 'competition') {
return parseInt(window.unity_game_number, 10)
} else {
if (!ismatch) {
return ((gamelv - 1) * 30 * gameNumber) + ((gameid - 1) * gameNumber) + this.BEGINNING_REAL_LVID
} else {
//TODO 这里维护一张匹配关卡映射表
if (gamelv === 240896 && gameid === 0) {
return 10000;
}
}
}
},
lastGameStyle: '',
lastGameDefaultSkin: 0,
checkGameStyle(realLvId) {
let gameStyle = 'SILU';
let gameDefaultSkin = 0;
if (realLvId >= this.BEGINNING_REAL_LVID && realLvId <= this.END_REAL_LVID) {
realLvId = realLvId - this.BEGINNING_REAL_LVID;
if (realLvId >= 1 && realLvId <= 300) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else if (realLvId >= 301 && realLvId <= 600) {
gameStyle = 'redArmy';
gameDefaultSkin = 2;
} else if (realLvId >= 601 && realLvId <= 900) {
gameStyle = 'numMan';
gameDefaultSkin = 3;
} else if (realLvId >= 901 && realLvId <= 1200) {
gameStyle = 'chinese';
gameDefaultSkin = 1;
} else if (realLvId >= 1201 && realLvId <= 1500) {
gameStyle = 'snow';
gameDefaultSkin = 4;
} else if (realLvId >= 1501 && realLvId <= 1800) {
gameStyle = 'SILU';
gameDefaultSkin = 0;
} else {
gameStyle = 'SILU';
gameDefaultSkin = 0;
}
}
if (gameStyle !== this.lastGameStyle) {
window.unityInstance.SendMessage("GameController", "ChangeUIStyle", gameStyle);
this.lastGameStyle = gameStyle;
}
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
if (gameDefaultSkin !== this.lastGameDefaultSkin) {
// 尝试多次发送消息,以确保 Unity 场景和 Player 对象已加载
const sendSkinMsg = (delay) => {
setTimeout(() => {
if (window.unityInstance) {
try {
window.unityInstance.SendMessage("Player", "CallChangeSkin", gameDefaultSkin);
console.log("CallChangeSkin sent >>> ", gameDefaultSkin);
} catch (e) {
console.log("Player not ready yet");
}
}
}, delay);
};
sendSkinMsg(2000);
sendSkinMsg(4000);
sendSkinMsg(6000);
this.lastGameDefaultSkin = gameDefaultSkin;
}
}
};
export default CONFIG;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
import disableDevtool from 'disable-devtool';
import CONFIG from './config.js';
const STOP_DEV_KEY = 'stopDev';
let stoppedForPage = false;
function isStopped() {
if (stoppedForPage) return true;
try {
return sessionStorage.getItem(STOP_DEV_KEY) === '1';
} catch (e) {
return false;
}
}
/**
* 关闭开发者工具检测,之后可以正常打开 F12。
* 可在控制台直接执行stopDev()
*/
export function stopDev() {
// 即使浏览器拒绝写入 sessionStorage当前页面仍应立即停止检测。
stoppedForPage = true;
try {
sessionStorage.setItem(STOP_DEV_KEY, '1');
} catch (e) {
// ignore quota / private mode
}
disableDevtool.isSuspend = true;
}
export function initDisableDevtool(options = {}) {
window.stopDev = stopDev;
if (isStopped()) {
disableDevtool.isSuspend = true;
// F12 已打开时,库可能在初始化期间立即触发检测,不能继续初始化。
return;
}
if (!CONFIG.disabledev) {
return;
}
const userIgnore = options.ignore;
const onDevtoolOpen = options.ondevtoolopen || (() => {
window.location.href = localStorage.getItem('returnUrl');
});
disableDevtool({
interval: 500,
clearLog: true,
disableMenu: true,
...options,
ondevtoolopen: (...args) => {
// stopDev() 之后,已排队的检测回调也不能清空代码或跳转。
if (isStopped()) return;
return onDevtoolOpen(...args);
},
ignore: () => {
if (isStopped()) {
return true;
}
if (typeof userIgnore === 'function') {
return userIgnore();
}
return false;
}
});
if (isStopped()) {
disableDevtool.isSuspend = true;
}
}
if (typeof window !== 'undefined') {
window.stopDev = stopDev;
}

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title> 首页 - 灵丌编程001CODE - 你身边的青少年编程学习平台 - 图形化、Python、C++</title>
<script src="js/nav-responsive.js?v=<%= htmlWebpackPlugin.options.version %>" defer></script>
</head>
<style>
:root {
@@ -73,6 +74,55 @@
gap: 32px;
}
.nav-toggle {
display: none;
width: 40px;
height: 40px;
margin-left: auto;
padding: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #f8fafc;
cursor: pointer;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
flex-shrink: 0;
appearance: none;
-webkit-appearance: none;
}
.nav-toggle:focus-visible {
outline: 2px solid rgba(79, 140, 255, 0.55);
outline-offset: 2px;
}
.nav-toggle span {
display: block;
width: 18px;
height: 2px;
background: #1e1a4c;
border-radius: 1px;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.nav-open .nav-toggle span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav-open .nav-toggle span:nth-child(2) {
opacity: 0;
}
.nav-open .nav-toggle span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
.nav-backdrop {
display: none;
}
@@ -86,7 +136,14 @@
}
.nav__link--active {
color: var(--brand);
display: inline-flex;
align-items: center;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
color: #4F8CFF;
box-sizing: border-box;
}
.nav__pill {
@@ -124,6 +181,87 @@
color: #ffffff;
}
/* 灵丌AI 导航胶囊 */
.nav__pillAI {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav__pillAI:hover {
transform: scale(1.05);
}
.nav__pillAI:focus {
outline: none;
}
.nav__pillAI:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav__pillAIText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 600;
font-size: 14px;
line-height: 1.5714285714;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIText {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav__pillAIIcon {
width: 18px;
height: 18px;
display: block;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIIcon {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
.hero {
position: relative;
height: 791px;
@@ -1111,6 +1249,81 @@
text-decoration: none;
}
@media (max-width: 960px) {
body.nav-open {
overflow: hidden;
}
.nav__inner {
gap: 12px;
padding: 4px 12px;
}
.nav__logo {
width: 156px;
height: 44px;
}
.nav-toggle {
display: inline-flex;
}
.nav__links {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
flex-direction: column;
align-items: stretch;
gap: 4px;
max-height: calc(100vh - 64px);
overflow-y: auto;
padding: 12px 16px 20px;
background: #ffffff;
border-top: 1px solid #e2e8f0;
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
z-index: 51;
}
.nav-open .nav__links {
display: flex;
}
.nav-backdrop {
position: fixed;
top: 64px;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.32);
z-index: 49;
}
.nav-open .nav-backdrop {
display: block;
}
.nav__links .nav__link,
.nav__links .nav__pillAI {
display: flex;
width: 100%;
min-height: 44px;
padding: 10px 12px;
border-radius: 10px;
align-items: center;
box-sizing: border-box;
}
.nav__links .nav__link:hover {
background: #f8fafc;
}
.nav__links .nav__pillAI:hover {
transform: none;
}
}
@media (max-width: 1280px) {
.feature__inner {
padding: 56px 24px;
@@ -1181,13 +1394,7 @@
.nav__logo {
width: 180px;
}
.nav__links {
gap: 16px;
overflow-x: auto;
padding-bottom: 6px;
width: 132px;
}
.progress {
@@ -1289,11 +1496,17 @@ top: 2032px; */
<header class="nav">
<div class="nav__inner">
<img class="nav__logo" src="../images/logo.svg" alt="001CODE" />
<button type="button" class="nav-toggle" aria-label="打开菜单" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
<nav class="nav__links" aria-label="主导航">
<a class="nav__link nav__link--active" href="/home.html">主页</a>
<a class="nav__pill" href="/">
<span class="nav__pillText">学习入口</span>
<span class="nav__pillNew">new</span>
<a class="nav__link" href="/welcome.html">灵丌编程</a>
<a class="nav__pillAI" href="/ai.html">
<img class="nav__pillAIIcon" src="../images/ai-sparkle.svg" alt="" />
<span class="nav__pillAIText">灵丌AI</span>
</a>
<a class="nav__link" href="/competition_list.html">赛事详情</a>
<a class="nav__link" href="/aboutus.html">关于我们</a>
@@ -1545,8 +1758,8 @@ top: 2032px; */
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -0,0 +1,3 @@
import { stopDev } from './devtool-control.js';
window.stopDev = stopDev;

View File

@@ -6,6 +6,9 @@ let timerId = localStorage.getItem('loginStatusCheckTimerId');
async function checkLoginStatus() {
const token = localStorage.getItem('access_token');
if (!token) {
try {
localStorage.setItem('returnUrl', window.location.pathname + window.location.search);
} catch (e) {}
window.location.href = window.location.origin + '/login.html';
return;
}

View File

@@ -14,6 +14,77 @@
<link rel="stylesheet" href="css/bootstrap.min.css?v=<%= htmlWebpackPlugin.options.version %>">
<link rel="stylesheet" href="css/bootstrap-icons.css?v=<%= htmlWebpackPlugin.options.version %>">
<link rel="stylesheet" href="css/leaderboard.css?v=<%= htmlWebpackPlugin.options.version %>">
<style>
/* 灵丌AI 导航链接 */
.nav-ai-link {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 6px 18px 6px 14px;
border-radius: 20px;
text-decoration: none;
white-space: nowrap;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-ai-link:hover {
transform: scale(1.05);
}
.nav-ai-link:focus {
outline: none;
}
.nav-ai-link:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav-ai-link__text {
font-size: 13px;
font-weight: 600;
font-style: italic;
letter-spacing: 0.5px;
/* 深蓝顶栏上使用浅色流光,保证可读 */
background-image: linear-gradient(
105deg,
rgba(255, 255, 255, 0.92) 0%,
rgba(255, 255, 255, 0.92) 38%,
#bae6fd 46%,
#ffffff 50%,
#7dd3fc 54%,
rgba(255, 255, 255, 0.92) 62%,
rgba(255, 255, 255, 0.92) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav-ai-link:hover .nav-ai-link__text {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav-ai-link img {
width: 16px;
height: 16px;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav-ai-link:hover img {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
</style>
</head>
<body class="leaderboard-page">
@@ -33,6 +104,10 @@
<div class="col-md-6">
<div class="header-info">
<div class="display-mode-controls">
<a class="nav-ai-link" href="/ai.html">
<img src="images/ai-sparkle.svg" alt="" />
<span class="nav-ai-link__text">灵丌AI</span>
</a>
<button id="fullscreen-btn" class="btn btn-outline-light" data-bs-toggle="tooltip"
title="大屏展示模式">
<i class="bi bi-fullscreen"></i>

View File

@@ -72,8 +72,25 @@
text-decoration: none;
}
/* 覆盖 nicepage 的 a:focus 黑色描边;仅键盘聚焦时保留可见提示 */
.nav a:focus {
outline: none;
}
.nav a:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav__link--active {
color: var(--brand);
display: inline-flex;
align-items: center;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
color: #4F8CFF;
box-sizing: border-box;
}
.nav__pill {
@@ -111,6 +128,78 @@
color: #ffffff;
}
/* 灵丌AI 导航胶囊 */
.nav__pillAI {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav__pillAI:hover {
transform: scale(1.05);
}
.nav__pillAIText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 600;
font-size: 14px;
line-height: 1.5714285714;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIText {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav__pillAIIcon {
width: 18px;
height: 18px;
display: block;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIIcon {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
/* End Copied Styles */
/* Mobile adjustments for nav (<=768px) */
@@ -185,9 +274,10 @@
</a>
<nav class="nav__links" aria-label="主导航">
<a class="nav__link" href="/home.html">主页</a>
<a class="nav__pill" href="/">
<span class="nav__pillText">学习入口</span>
<span class="nav__pillNew">new</span>
<a class="nav__link" href="/welcome.html">灵丌编程</a>
<a class="nav__pillAI" href="/ai.html">
<img class="nav__pillAIIcon" src="images/ai-sparkle.svg" alt="" />
<span class="nav__pillAIText">灵丌AI</span>
</a>
<a class="nav__link" href="/competition_list.html">赛事详情</a>
<a class="nav__link" href="/aboutus.html">关于我们</a>
@@ -256,8 +346,8 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -52,7 +52,7 @@
<div class="login-card">
<h1 class="card-title">账号登录</h1>
<form id="login-form" action="" class="login-form" name="form">
<form id="login-form" action="javascript:void(0)" class="login-form" name="form">
<div class="form-group">
<label for="name-4c18" class="form-label">手机号码 *</label>
<div class="input-wrapper">
@@ -95,8 +95,8 @@
<footer class="site-footer">
<div class="footer-divider"></div>
<p class="footer-text"> <p class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<p class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM</p></p>
</footer>

View File

@@ -1,23 +1,12 @@
// 引入配置文件
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
let is_permissionOKUser = false;
document.addEventListener('DOMContentLoaded', async function () {
// 初始化禁用开发者工具
if (CONFIG.disabledev) {
disableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
window.location.href = localStorage.getItem('returnUrl');
},
interval: 500,
clearLog: true,
disableMenu: true,
});
}
initDisableDevtool();
// 登录相关常量
const MAX_ATTEMPTS = 5;
@@ -202,7 +191,7 @@ document.addEventListener('DOMContentLoaded', async function () {
imageUrl = '/images/anhui.png';
}
else if (result.qrid === 240877 && result.cityid === 164) {
else if (result.qrid === 240877 && result.cityid === 164 && result.districtid === 1320) {
imageUrl = '/images/liaocheng.png';
}
@@ -213,11 +202,14 @@ document.addEventListener('DOMContentLoaded', async function () {
else if (result.qrid === 240877 && result.cityid === 152) {
imageUrl = '/images/shandong-zaozhaung.jpg';
}
else if (result.qrid === 240870) {//河北省全区域
imageUrl = '/images/wang.jpg';
}
else {
const group = {
240875: "ran.png", 240877: "ran.png", 240889: "ran.png", 240873: "ran.png", 240893: "ran.png", 240870: "ran.png", 240878: "ran.png", 240874: "ran.png", 240876: "ran.png", 240891: "ran.png",
240875: "ran.png", 240877: "wang.jpg", 240870: 'wang.jpg', 240889: "ran.png", 240873: "ran.png", 240893: "ran.png", 240878: "ran.png", 240874: "ran.png", 240876: "ran.png", 240891: "ran.png",
240897: "zhushou.png", 240882: "sichuan.png", 240895: "zhushou.png", 240881: "zhushou.png", 240894: "zhushou.png", 240879: "zhushou.png", 240885: "zhushou.png", 240883: "zhushou.png", 240884: "zhushou.png", 240899: "zhushou.png",
240892: "zhoushou2.png", 240871: "zhoushou2.png", 240887: "zhoushou2.png", 240872: "zhoushou2.png", 240888: "zhoushou2.png", 240886: "zhoushou2.png", 240880: "zhoushou2.png", 240900: "zhoushou2.png", 240898: "zhoushou2.png", 240890: "zhoushou2.png", 240896: "zhoushou2.png"
};
@@ -232,8 +224,20 @@ document.addEventListener('DOMContentLoaded', async function () {
}
sessionStorage.setItem('qrcode_url', imageUrl);
await api.playerOnline(result.player_id, result.access);
localStorage.setItem('returnUrl', `${window.location.origin}/login.html`);
window.location.href = `${window.location.origin}/welcome.html`;
// 登录成功跳回来源页(各页面跳转登录前会写 returnUrl默认回 welcome.html
let target = '/welcome.html';
try {
const returnUrl = localStorage.getItem('returnUrl');
if (returnUrl) {
localStorage.removeItem('returnUrl');
// 仅接受站内相对路径,防止开放重定向
if (typeof returnUrl === 'string' && returnUrl.startsWith('/') &&
!returnUrl.startsWith('//') && !returnUrl.startsWith('/login')) {
target = returnUrl;
}
}
} catch (e) {}
window.location.href = `${window.location.origin}${target}`;
};
// 处理登录失败
@@ -242,7 +246,7 @@ document.addEventListener('DOMContentLoaded', async function () {
// uiUtils.triggerModalLink("carousel_ac25");
const modalImageElement = document.querySelector('#carousel_ac25 .modal-qr');
if (modalImageElement) {
console.error(result, '111aaa');
console.error(result, '代理商数据');
let imageUrl = '/images/default_qrcode.png';
if (result.qrid === 240896) {
@@ -264,11 +268,13 @@ document.addEventListener('DOMContentLoaded', async function () {
}
else if (result.qrid === 240877 && result.cityid === 152) {
imageUrl = '/images/shandong-zaozhaung.jpg';
}else if (result.qrid === 240870) { //河北省全区域
imageUrl = '/images/wang.jpg';
}
else {
const group = {
240875: "ran.png", 240877: "ran.png", 240889: "ran.png", 240873: "ran.png", 240893: "ran.png", 240870: "ran.png", 240878: "ran.png", 240874: "ran.png", 240876: "ran.png", 240891: "ran.png",
240875: "ran.png", 240877: "wang.jpg", 240889: "ran.png", 240873: "ran.png", 240893: "ran.png", 240870: "wang.jpg", 240878: "ran.png", 240874: "ran.png", 240876: "ran.png", 240891: "ran.png",
240897: "zhushou.png", 240882: "sichuan.png", 240895: "zhushou.png", 240881: "zhushou.png", 240894: "zhushou.png", 240879: "zhushou.png", 240885: "zhushou.png", 240883: "zhushou.png", 240884: "zhushou.png", 240899: "zhushou.png",
240892: "zhoushou2.png", 240871: "zhoushou2.png", 240887: "zhoushou2.png", 240872: "zhoushou2.png", 240888: "zhoushou2.png", 240886: "zhoushou2.png", 240880: "zhoushou2.png", 240900: "zhoushou2.png", 240898: "zhoushou2.png", 240890: "zhoushou2.png", 240896: "zhoushou2.png"
};

View File

@@ -1,22 +1,11 @@
// matchsum.js
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
sessionStorage.setItem('lastpage', 'matchsum.html');
let gamelvdata = {};
document.addEventListener('DOMContentLoaded', function () {
if (CONFIG.disabledev) {
disableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
window.location.href = localStorage.getItem('returnUrl');
},
interval: 500,
clearLog: true,
disableMenu: true,
});
}
initDisableDevtool();
// 初始化 window.gamelvdata
if (!window.gamelvdatas) {
window.gamelvdatas = {"gamemode": 1, "gamelv": 1, "gameid": 1, "currentlv": 1};

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>
@@ -92,10 +92,9 @@
<footer class="footer">
<div class="footer-inner">
<div class="footer-text">
ICP备案号<a style=" color: #387cbd;" href=" " target="_blank">蜀ICP备2022002563号-5</a>
<span> 公安备案号:<a style=" color: #387cbd;" href="https://beian.mps.gov.cn/"
target="_blank">川公网安备51011202000980号</a></span>
<div class="footer-text" ">
ICP备案号<a style=" color: #387cbd;" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
<span>公安备案号:<a style="color:#387cbd;" href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a></span>
<span>© 灵丌编程001CODE.COM</span>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
// ============= 业务参数 =============
let gamelvid = '1'; // 具体关卡等级,初始为 1
@@ -51,16 +51,11 @@ document.addEventListener('DOMContentLoaded', function () {
updateLanguageDisplay();
// 初始化禁用开发者工具
if (CONFIG.disabledev) {
disableDevtool({
ondevtoolopen: () => {
window.location.href = localStorage.getItem('returnUrl') || '/welcome.html';
},
interval: 500,
clearLog: true,
disableMenu: true,
});
}
initDisableDevtool({
ondevtoolopen: () => {
window.location.href = localStorage.getItem('returnUrl') || '/welcome.html';
}
});
@@ -111,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: 4 },
4: { title: "Level D <span style='font-size: 0.7em'>国宝秘境</span>", sub: "条件分支进阶,循环嵌套入门,循环嵌套进阶,变量、循环与条件综合应用", cover: 4 },
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');
@@ -129,11 +124,17 @@ 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();
gamelvdata.gamelv = index + 1;
// 记住当前 Level从关卡返回后恢复
try {
sessionStorage.setItem('gamelvdata', JSON.stringify(gamelvdata));
sessionStorage.setItem('programmingLearningSelectedLevel', String(lv));
} catch (e) {}
console.log('当前 gamelvid:', gamelvid);
if (levelDataCache[gamelvid]) {
@@ -145,10 +146,21 @@ document.addEventListener('DOMContentLoaded', function () {
});
});
// Trigger default click
// 恢复上次选中的 Level默认 Level A
if (listItemsModes.length > 0) {
// "触发默认点击... 让用户一进页面就能看到默认关卡"
listItemsModes[0].click();
let savedLv = parseInt(
(gamelvdata && gamelvdata.gamelv) ||
sessionStorage.getItem('programmingLearningSelectedLevel') ||
'1',
10
);
if (!isFinite(savedLv) || savedLv < 1) savedLv = 1;
if (savedLv > listItemsModes.length) savedLv = listItemsModes.length;
// 无权限时只能回 Level A
if (savedLv !== 1 && (!permission || parseInt(permission) === 0)) {
savedLv = 1;
}
listItemsModes[savedLv - 1].click();
}
/********************************************************

View File

@@ -66,7 +66,14 @@
}
.nav__link--active {
color: var(--brand);
display: inline-flex;
align-items: center;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
color: #4F8CFF;
box-sizing: border-box;
}
.nav__pill {
@@ -104,34 +111,89 @@
color: #ffffff;
}
/* Mobile adjustments for nav (<=768px) */
@media (max-width: 768px) {
.nav__inner {
padding: 6px 12px;
gap: 12px;
align-items: center;
}
/* 灵丌AI 导航胶囊 */
.nav__pillAI {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav__logo {
width: 140px;
height: auto;
}
.nav__pillAI:hover {
transform: scale(1.05);
}
.nav__pillAI:focus {
outline: none;
}
.nav__pillAI:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav__pillAIText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 600;
font-size: 14px;
line-height: 1.5714285714;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIText {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav__pillAIIcon {
width: 18px;
height: 18px;
display: block;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav__pillAI:hover .nav__pillAIIcon {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
.nav__links {
gap: 12px;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.nav__link,
.nav__pillText {
font-size: 11px;
}
.nav__pillNew {
display: none;
}
}
</style>
</head>
@@ -143,9 +205,10 @@
</a>
<nav class="nav__links" aria-label="主导航">
<a class="nav__link" href="/home.html">主页</a>
<a class="nav__pill" href="/">
<span class="nav__pillText">学习入口</span>
<span class="nav__pillNew">new</span>
<a class="nav__link" href="/welcome.html">灵丌编程</a>
<a class="nav__pillAI" href="/ai.html">
<img class="nav__pillAIIcon" src="images/ai-sparkle.svg" alt="" />
<span class="nav__pillAIText">灵丌AI</span>
</a>
<a class="nav__link" href="/competition_list.html">赛事详情</a>
<a class="nav__link" href="/aboutus.html">关于我们</a>
@@ -297,8 +360,8 @@
<footer class="footer" aria-label="页脚">
<div class="footer-line"></div>
<div class="footer-text">ICP备案号<a href=" " target="_blank" style="color:#478ac9 ;">ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank" style="color:#478ac9 ;">川公网安备51011202000980号</a>
<div class="footer-text">ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" style="color:#478ac9 ;">ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank" style="color:#478ac9 ;"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a>
© 灵丌编程001CODE.COM
<span class="qr" tabindex="0">
<img class="qr-icon" src="../images/qrcode_gery.svg" alt="公众号小图标" />

View File

@@ -1,5 +1,5 @@
import CONFIG from './config.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
// 获取URL参数的函数
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
@@ -88,18 +88,7 @@ function showError(message) {
// 页面加载完成后执行
document.addEventListener('DOMContentLoaded', () => {
if (CONFIG.disabledev) {
disableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
window.location.href = localStorage.getItem('returnUrl');
},
interval: 500,
clearLog: true,
disableMenu: true,
});
}
initDisableDevtool();
const {thirdToken, returnUrl} = getUrlParams();

View File

@@ -13,6 +13,101 @@
defer></script>
<!-- <script class="u-script" type="text/javascript"
src="rank-badge-generator.js?v=<%= htmlWebpackPlugin.options.version %>" defer></script> -->
<style>
/* 灵丌编程 — 与 ai.html 一致 */
.nav__pill {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
text-decoration: none;
flex-shrink: 0;
}
.nav__pillText {
font-family: "PingFang SC", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 1.5714285714;
color: #4F8CFF;
white-space: nowrap;
}
.nav__pillNew {
display: none;
}
/* 灵丌AI 导航胶囊 */
.nav-ai-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 18px 5px 14px;
height: 32px;
border-radius: 20px;
text-decoration: none;
flex-shrink: 0;
position: relative;
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.nav-ai-pill:hover {
transform: scale(1.05);
}
.nav-ai-pill:focus {
outline: none;
}
.nav-ai-pill:focus-visible {
outline: 2px solid rgba(0, 3, 255, 0.45);
outline-offset: 3px;
}
.nav-ai-pill span {
font-weight: 600;
font-size: 13px;
font-style: italic;
letter-spacing: 0.5px;
background-image: linear-gradient(
105deg,
rgba(0, 3, 255, 0.55) 0%,
rgba(0, 3, 255, 0.55) 38%,
#7dd3fc 46%,
#ffffff 50%,
#60a5fa 54%,
rgba(0, 3, 255, 0.55) 62%,
rgba(0, 3, 255, 0.55) 100%
);
background-size: 220% 100%;
background-position: 100% 50%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: letter-spacing 0.35s ease;
}
.nav-ai-pill:hover span {
letter-spacing: 1.2px;
animation: navAIShimmer 1.35s ease-in-out infinite;
}
@keyframes navAIShimmer {
0% { background-position: 100% 50%; }
100% { background-position: -120% 50%; }
}
.nav-ai-pill img {
width: 18px;
height: 18px;
flex-shrink: 0;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), filter 0.35s ease;
}
.nav-ai-pill:hover img {
transform: rotate(18deg) scale(1.18);
filter: brightness(1.35) saturate(1.25);
animation: navAISpark 1.1s ease-in-out infinite;
}
@keyframes navAISpark {
0%, 100% { transform: rotate(18deg) scale(1.18); }
50% { transform: rotate(-8deg) scale(1.28); }
}
</style>
</head>
<body data-path-to-root="./" data-include-products="false" class="u-body u-xl-mode" data-lang="en">
@@ -23,33 +118,70 @@
<div class="navbar-logo" onclick="window.location.href='/home.html'" style="cursor:pointer;"></div>
<!-- <div class="navbar-title">001CODE</div> -->
</div>
<div class="navbar-middle">
<div class="nav-link-group">
<a class="nav-link" href="home.html">首页</a>
</div>
<div class="nav-link-group" id="user-rank-badge" title="Rank Badge">
<span class="nav-link nav-label">当前段位:</span>
<span class="nav-rank-icon"></span>
<span class="nav-link nav-rank-text">加载中...</span>
</div>
</div>
<div class="navbar-right">
<a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a>
<a class="navbar-link u-dialog-link" href="#carousel_c286">修改密码</a>
<a class="navbar-link navbar-btn-activate u-dialog-link" href="#carousel_ac25" style="text-decoration:none;">
<div style="display:flex;align-items:center;gap:4px;">
<span>开通权益</span>
<span class="navbar-link-vip" style="display:none;">VIP</span>
<button type="button" class="navbar-toggle" id="navbarToggle" aria-label="打开菜单" aria-expanded="false" aria-controls="navbarCollapse">
<span></span>
<span></span>
<span></span>
</button>
<div class="navbar-collapse" id="navbarCollapse">
<div class="navbar-middle">
<div class="nav-link-group">
<a class="nav-link" href="home.html">首页</a>
</div>
</a>
<div class="navbar-account">
<!-- <div class="navbar-avatar"></div> -->
<div class="navbar-logout" id="logout-button">退出</div>
<div class="nav-link-group">
<span class="nav__pill" aria-current="page" style="cursor:default;">
<span class="nav__pillText">灵丌编程</span>
</span>
</div>
<div class="nav-link-group">
<a class="nav-ai-pill" href="/ai.html">
<img src="../images/ai-sparkle.svg" alt="" />
<span>灵丌AI</span>
</a>
</div>
<div class="nav-link-group" id="user-rank-badge" title="Rank Badge">
<span class="nav-link nav-label">当前段位:</span>
<span class="nav-rank-icon"></span>
<span class="nav-link nav-rank-text">加载中...</span>
</div>
</div>
<div class="navbar-right">
<a class="navbar-link" id="open-personal-info" href="javascript:void(0);">个人信息</a>
<a class="navbar-link u-dialog-link" href="#carousel_c286">修改密码</a>
<a class="navbar-link navbar-btn-activate u-dialog-link" href="#carousel_ac25" style="text-decoration:none;">
<div style="display:flex;align-items:center;gap:4px;">
<span>开通权益</span>
<span class="navbar-link-vip" style="display:none;">VIP</span>
</div>
</a>
<div class="navbar-account">
<!-- <div class="navbar-avatar"></div> -->
<div class="navbar-logout" id="logout-button">退出</div>
</div>
<a class="navbar-login" href="/login.html" id="navbarLoginBtn">去登录</a>
</div>
</div>
</div>
<div class="navbar-backdrop" id="navbarBackdrop"></div>
</header>
<script>
(function () {
try {
if (!localStorage.getItem('access_token')) {
var nav = document.querySelector('.navbar-wrapper');
if (nav) nav.classList.add('is-guest');
var loginBtn = document.getElementById('navbarLoginBtn');
if (loginBtn) {
loginBtn.addEventListener('click', function () {
try { localStorage.setItem('returnUrl', '/welcome.html'); } catch (e) {}
});
}
}
} catch (e) {}
})();
</script>
<div class="glass-container">
<main class="main">
@@ -199,8 +331,8 @@
<footer class="footer">
<div class="footer-inner">
<div class="footer-text">
ICP备案号<a href=" " target="_blank">蜀ICP备2022002563号-5</a>
公安备案号:<a href="https://beian.mps.gov.cn/" target="_blank">川公网安备51011202000980号</a> © 灵丌编程001CODE.COM
ICP备案号<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">沪ICP备2026042325号</a>
公安备案号:<a href="https://beian.mps.gov.cn/#/query/webSearch?code=31011502407417" rel="noreferrer" target="_blank"><img src="../images/gongan-beian.png" alt="" width="16" height="16" style="width:16px;height:16px;vertical-align:-3px;margin-right:4px;" />沪公网安备31011502407417号</a> © 灵丌编程001CODE.COM
</div>
</div>
</footer>
@@ -778,8 +910,11 @@
} catch (error) { console.error(error); }
}
await fetchUserDetails();
await fetchUserRank();
const hasAccessToken = !!localStorage.getItem('access_token');
if (hasAccessToken) {
await fetchUserDetails();
await fetchUserRank();
}
// Edit Profile Logic
let isEditing = false;

View File

@@ -1,6 +1,6 @@
import CONFIG from './config.js';
import RankBadgeGenerator from './rank-badge-generator.js';
import disableDevtool from 'disable-devtool';
import { initDisableDevtool } from './devtool-control.js';
// 好友按钮逻辑已移除
@@ -83,18 +83,7 @@ window.addEventListener('DOMContentLoaded', () => {
// 检查登录状态
// checkLoginStatus();
// 初始化禁用开发者工具
if (CONFIG.disabledev) {
disableDevtool({
ondevtoolopen: () => {
// window.location.href = '/index.html';
window.location.href = localStorage.getItem('returnUrl');
},
interval: 500,
clearLog: true,
disableMenu: true,
});
}
initDisableDevtool();
// 设置二维码图片
setQRCodeImage();
@@ -105,6 +94,9 @@ window.addEventListener('DOMContentLoaded', () => {
// 初始化权限控制
initPermissionControl();
// 小屏导航折叠
initNavbarToggle();
// 绑定按钮事件
bindButtonEvents();
@@ -129,6 +121,44 @@ if (logoutBtn) {
});
}
function initNavbarToggle() {
const wrapper = document.querySelector('.navbar-wrapper');
const toggle = document.getElementById('navbarToggle');
const collapse = document.getElementById('navbarCollapse');
const backdrop = document.getElementById('navbarBackdrop');
if (!wrapper || !toggle || !collapse) return;
const setOpen = (open) => {
wrapper.classList.toggle('is-open', open);
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
toggle.setAttribute('aria-label', open ? '关闭菜单' : '打开菜单');
document.body.classList.toggle('nav-open', open);
};
toggle.addEventListener('click', (event) => {
event.stopPropagation();
setOpen(!wrapper.classList.contains('is-open'));
});
if (backdrop) {
backdrop.addEventListener('click', () => setOpen(false));
}
collapse.addEventListener('click', (event) => {
if (event.target.closest('a, .navbar-logout, #open-personal-info')) {
setOpen(false);
}
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') setOpen(false);
});
window.addEventListener('resize', () => {
if (window.innerWidth > 960) setOpen(false);
});
}
// 初始化轮播区域
function initCarouselSection() {
const carouselSection = document.getElementById('sec-df5f');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,294 @@
/* AI 页:个人信息 / 修改密码弹窗(从 welcome-new.css 迁移) */
.modal-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(0, 0, 0, 0.45);
z-index: 9999;
}
.modal-basic-info {
width: 720px;
max-width: calc(100vw - 48px);
background: #FFFFFF;
border-radius: 10px;
box-shadow: 0px 34px 44px -20px rgba(185, 206, 234, 0.25);
padding: 32px;
display: flex;
flex-direction: column;
gap: 16px;
}
.modal-header {
display: flex;
align-items: center;
gap: 16px;
width: 100%;
}
.modal-title {
flex: 1;
font-family: Nunito, system-ui, sans-serif;
font-weight: 800;
font-size: 28px;
line-height: 1.2;
color: #1E293B;
text-align: left;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.modal-subtitle {
font-family: "PingFang SC", system-ui, sans-serif;
font-size: 14px;
font-weight: 400;
color: #94A3B8;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.modal-close {
width: 32px;
height: 32px;
border-radius: 3px;
padding: 2px;
border: 0;
background: transparent;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.modal-body {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.modal-actions {
display: flex;
justify-content: center;
}
.modal-primary-btn {
border: 0;
cursor: pointer;
border-radius: 32px;
background: linear-gradient(90deg, rgba(3, 161, 234, 1) 0%, rgba(32, 91, 175, 1) 100%);
color: #FFFFFF;
padding: 10px 18px;
height: 44px;
font-family: "Noto Sans SC", "PingFang SC", system-ui, sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap;
}
.modal-password {
width: 450px;
max-width: calc(100vw - 48px);
background: #FFFFFF;
border-radius: 10px;
box-shadow: 0px 34px 44px -20px rgba(185, 206, 234, 0.25);
padding: 32px;
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
}
.modal-header-spacer {
width: 32px;
height: 32px;
flex-shrink: 0;
}
.modal-password .field {
width: 100%;
display: flex;
flex-direction: column;
gap: 15px;
}
.modal-password .field-label {
font-family: Inter, system-ui, sans-serif;
font-weight: 500;
font-size: 16px;
line-height: 19.36px;
color: #000000;
}
.input-gradient {
width: 100%;
border-radius: 8px;
padding: 1px;
background: linear-gradient(164deg, rgba(226, 230, 255, 1) 24%, rgba(255, 234, 227, 1) 95%);
}
.modal-password .field-input {
width: 100%;
height: 44px;
border-radius: 7px;
border: 0;
padding: 0 12px;
background: #F5F6F9;
font-family: "PingFang SC", system-ui, sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #0F172A;
outline: none;
box-sizing: border-box;
}
.modal-password .field-input::placeholder {
color: #7D91A1;
}
.primary-btn {
width: 180px;
height: 48px;
border: 0;
border-radius: 32px;
background: linear-gradient(90deg, rgba(3, 161, 234, 1) 0%, rgba(32, 91, 175, 1) 100%);
color: #FFFFFF;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
font-family: "Noto Sans SC", system-ui, sans-serif;
font-weight: 400;
font-size: 14px;
line-height: 20px;
white-space: nowrap;
}
.profile-body {
width: 100%;
display: flex;
flex-direction: column;
gap: 32px;
}
.profile-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
width: 100%;
}
@media (max-width: 640px) {
.profile-grid {
grid-template-columns: 1fr;
}
}
.profile-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.profile-label {
font-family: "PingFang SC", system-ui, sans-serif;
font-size: 14px;
color: #64748B;
font-weight: 500;
}
.profile-input-wrap {
display: flex;
align-items: center;
background-color: #F8FAFC;
border: 1px solid #E2E8F0;
border-radius: 8px;
height: 48px;
padding: 0 16px;
transition: all 0.2s ease;
}
.profile-input-wrap:focus-within {
background-color: #FFFFFF;
border-color: #3B82F6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.profile-icon {
width: 20px;
height: 20px;
color: #94A3B8;
margin-right: 12px;
flex-shrink: 0;
}
.profile-input-wrap:focus-within .profile-icon {
color: #3B82F6;
}
.profile-input {
flex: 1;
border: none;
background: transparent;
height: 100%;
font-family: inherit;
font-size: 14px;
color: #1E293B;
outline: none;
width: 100%;
}
.profile-value {
flex: 1;
font-size: 14px;
color: #1E293B;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
select.profile-input {
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0 center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
.profile-input-wrap.is-vip {
background: linear-gradient(to right, #FFFBF0, #FFF7E6);
border-color: #FDE68A;
}
.profile-input-wrap.is-vip .profile-icon {
color: #D97706;
}
.profile-value-btn {
background: transparent;
border: none;
color: #D97706;
font-weight: 600;
font-size: 14px;
padding: 0;
cursor: default;
}
.profile-input:disabled {
background-color: transparent;
color: #64748B;
cursor: not-allowed;
}

View File

@@ -60,6 +60,63 @@ body {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.navbar-collapse {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.navbar-toggle {
display: none;
width: 40px;
height: 40px;
margin-left: 12px;
padding: 0;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #f8fafc;
cursor: pointer;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
flex-shrink: 0;
appearance: none;
-webkit-appearance: none;
}
.navbar-toggle:focus-visible {
outline: 2px solid rgba(79, 140, 255, 0.55);
outline-offset: 2px;
}
.navbar-toggle span {
display: block;
width: 18px;
height: 2px;
background: #1e1a4c;
border-radius: 1px;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.navbar-wrapper.is-open .navbar-toggle span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.navbar-wrapper.is-open .navbar-toggle span:nth-child(2) {
opacity: 0;
}
.navbar-wrapper.is-open .navbar-toggle span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
.navbar-backdrop {
display: none;
}
.navbar-brand {
@@ -110,12 +167,24 @@ body {
font-weight: 400;
font-size: 14px;
line-height: 1.43;
color: #0790D9;
color: #1E1A4C;
text-decoration: none;
cursor: pointer;
white-space: nowrap;
}
.nav-link--active,
.nav-link[aria-current="page"] {
display: inline-flex;
align-items: center;
padding: 5px 12px;
height: 32px;
border-radius: 3px;
background: #f2f3ff;
color: #4F8CFF;
box-sizing: border-box;
}
.nav-label {
color: #1E1A4C;
}
@@ -232,6 +301,38 @@ body {
white-space: nowrap;
}
.navbar-login {
display: none;
align-items: center;
justify-content: center;
padding: 8px 24px;
border-radius: 20px;
background: linear-gradient(90deg, rgba(3, 161, 234, 1) 0%, rgba(32, 91, 175, 1) 100%);
color: #ffffff;
font-size: 14px;
font-weight: 600;
line-height: 1.43;
text-decoration: none;
white-space: nowrap;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.navbar-login:hover {
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(3, 161, 234, 0.35);
}
/* 未登录游客态:不显示段位与右侧入口,只显示「去登录」 */
.navbar-wrapper.is-guest #user-rank-badge,
.navbar-wrapper.is-guest .navbar-right .navbar-link,
.navbar-wrapper.is-guest .navbar-right .navbar-account {
display: none;
}
.navbar-wrapper.is-guest .navbar-login {
display: inline-flex;
}
.glass-container {
flex: 1;
display: flex;
@@ -411,7 +512,7 @@ body {
}
/* .course-card-image.course-1 {
background-image: url("../images/image-119.png");
background-image: url("../images/default_compertion.png");
}
.course-card-image.course-2 {
@@ -555,13 +656,149 @@ body {
display: inline-block;
}
@media (max-width: 960px) {
@media (max-width: 1200px) {
.navbar-logo {
width: 180px;
}
.navbar-middle {
display: none;
gap: 16px;
margin-left: 16px;
}
.navbar-right {
gap: 10px;
}
}
@media (max-width: 960px) {
body.nav-open {
overflow: hidden;
}
.navbar {
gap: 12px;
gap: 8px;
}
.navbar-logo {
width: 156px;
height: 44px;
}
.navbar-toggle {
display: inline-flex;
margin-left: auto;
}
.navbar-collapse {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
flex: none;
flex-direction: column;
align-items: stretch;
gap: 8px;
max-height: calc(100vh - 64px);
overflow-y: auto;
padding: 12px 16px 20px;
background: #ffffff;
border-top: 1px solid #e2e8f0;
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.12);
z-index: 1001;
}
.navbar-wrapper.is-open .navbar-collapse {
display: flex;
}
.navbar-backdrop {
display: none;
position: fixed;
top: 64px;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.32);
z-index: 1000;
}
.navbar-wrapper.is-open .navbar-backdrop {
display: block;
}
.navbar-middle,
.navbar-right {
flex-direction: column;
align-items: stretch;
width: 100%;
margin: 0;
gap: 4px;
}
.navbar-middle {
padding-bottom: 8px;
border-bottom: 1px solid #eef2f7;
}
.navbar-right {
padding-top: 4px;
justify-content: flex-start;
}
.nav-link-group,
.navbar-link,
.navbar-account {
width: 100%;
min-height: 44px;
padding: 10px 12px;
border-radius: 10px;
justify-content: flex-start;
}
.nav-link-group:hover,
.navbar-link:hover,
.navbar-account:hover {
background: #f8fafc;
}
.navbar-wrapper.is-guest .navbar-login {
width: 100%;
min-height: 44px;
margin-top: 4px;
}
.navbar-collapse .nav__pill,
.navbar-collapse .nav-ai-pill {
width: 100%;
height: 40px;
justify-content: flex-start;
}
.navbar-collapse .nav-ai-pill:hover {
transform: none;
}
.navbar-btn-activate {
justify-content: center;
margin-top: 4px;
}
.navbar-btn-activate:hover {
background: linear-gradient(135deg, #ffc107 0%, #ff8f00 100%) !important;
transform: none;
box-shadow: 0 4px 12px rgba(255, 143, 0, 0.3);
}
}
@media (max-width: 480px) {
.container {
padding: 0 12px;
}
.navbar-logo {
width: 132px;
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 MiB

View File

@@ -0,0 +1,19 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="ai-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00CCFF"/>
<stop offset="50%" stop-color="#0066FF"/>
<stop offset="100%" stop-color="#0003FF"/>
</linearGradient>
</defs>
<!-- 中心大星 -->
<path d="M10 0.5L11.8 6.5L10 8.2L8.2 6.5L10 0.5Z" fill="url(#ai-grad)"/>
<path d="M10 19.5L8.2 13.5L10 11.8L11.8 13.5L10 19.5Z" fill="url(#ai-grad)"/>
<path d="M0.5 10L6.5 8.2L8.2 10L6.5 11.8L0.5 10Z" fill="url(#ai-grad)"/>
<path d="M19.5 10L13.5 11.8L11.8 10L13.5 8.2L19.5 10Z" fill="url(#ai-grad)"/>
<!-- 对角小星 -->
<path d="M3.5 3.5L6.5 5.5L5.5 6.5L3.5 5.5L2.5 3.5L3.5 3.5Z" fill="url(#ai-grad)" opacity="0.7"/>
<path d="M16.5 3.5L13.5 5.5L14.5 6.5L16.5 5.5L17.5 3.5L16.5 3.5Z" fill="url(#ai-grad)" opacity="0.7"/>
<path d="M3.5 16.5L6.5 14.5L5.5 13.5L3.5 14.5L2.5 16.5L3.5 16.5Z" fill="url(#ai-grad)" opacity="0.7"/>
<path d="M16.5 16.5L13.5 14.5L14.5 13.5L16.5 14.5L17.5 16.5L16.5 16.5Z" fill="url(#ai-grad)" opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

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