Compare commits
14 Commits
aaccb73773
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb78d8bf5c | ||
|
|
c4641aee19 | ||
|
|
782f76170f | ||
|
|
d83654837a | ||
|
|
899adbc9d4 | ||
|
|
4499d7fdef | ||
|
|
11d7496b54 | ||
|
|
1012af6039 | ||
|
|
37e0413576 | ||
|
|
8dfd42397f | ||
|
|
19bb63cd87 | ||
|
|
bae333ba7c | ||
|
|
badbc11154 | ||
|
|
9b6892f2ed |
BIN
docker-conf/.DS_Store
vendored
Normal file
BIN
docker-conf/conf.d/.DS_Store
vendored
Normal 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;
|
||||
}
|
||||
|
||||
# 静态资源缓存配置
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
# 静态资源缓存配置
|
||||
@@ -78,4 +81,4 @@ server {
|
||||
# 错误日志和访问日志配置
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
scratch-gui/competitio_login/assets/gongan-beian.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -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>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
71
scratch-gui/scripts/diagnose-cpp.cjs
Normal 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;});
|
||||
@@ -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 };
|
||||
|
||||
139
scratch-gui/scripts/test-cpp-regressions.cjs
Normal 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;});
|
||||
166
scratch-gui/scripts/test-devtool-control.cjs
Normal 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`);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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{
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} />
|
||||
|
||||
|
||||
|
||||
@@ -1890,4 +1905,4 @@ UnityComponent.propTypes = {
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
export default UnityComponent;
|
||||
export default UnityComponent;
|
||||
|
||||
@@ -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}`);
|
||||
@@ -1323,7 +1404,15 @@ 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;
|
||||
|
||||
// 检查是否是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;
|
||||
}
|
||||
|
||||
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
});
|
||||
} 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;
|
||||
|
||||
// 检查是否是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;
|
||||
}
|
||||
|
||||
// 使用预先计算好的精确行号,如果没有则使用调整后的行号
|
||||
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)}`);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 在每个语句执行后重新检查循环条件(特别重要!)
|
||||
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 = '';
|
||||
|
||||
for (let i = 0; i < expr.length; i++) {
|
||||
const char = expr[i];
|
||||
|
||||
if (operators.includes(char)) {
|
||||
if (current.trim()) {
|
||||
tokens.push(current.trim());
|
||||
current = '';
|
||||
}
|
||||
tokens.push(char);
|
||||
peekExprToken() {
|
||||
return this._exprTokens[this._exprPos] || { type: 'eof' };
|
||||
}
|
||||
|
||||
consumeExprToken() {
|
||||
return this._exprTokens[this._exprPos++] || { type: 'eof' };
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -2591,8 +2606,7 @@ 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]);
|
||||
@@ -2604,7 +2618,7 @@ export class CppCodeExecutor {
|
||||
if (token.includes('vehicle_position()')) {
|
||||
return window.cppGameAPI.vehicle_position();
|
||||
}
|
||||
|
||||
|
||||
debugLog(`⚠️ 无法解析标记: ${token}`);
|
||||
return 0;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -3761,4 +3775,4 @@ export const testCompleteIfElseIfElse = async () => {
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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++执行环境
|
||||
@@ -660,7 +654,7 @@ const StageWrapperCppClang = () => {
|
||||
if (window.unityInstance) {
|
||||
window.unityInstance.SendMessage("UIMain", "SetText", window.cmd_print_text);
|
||||
}
|
||||
|
||||
|
||||
setCompilationOutput(prev => prev + textStr + "\n");
|
||||
},
|
||||
|
||||
@@ -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,170 +1216,141 @@ 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StageWrapperCppClang;
|
||||
export default StageWrapperCppClang;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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} />
|
||||
|
||||
|
||||
|
||||
@@ -1876,4 +1891,4 @@ UnityComponent.propTypes = {
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
export default UnityComponent;
|
||||
export default UnityComponent;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
@@ -649,15 +667,65 @@ const StageWrapperPythonCPLT = () => {
|
||||
if (t) parts.push(t);
|
||||
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.apiDrawerTab} ${apiDrawerOpen ? styles.apiDrawerTabOpen : ''}`}
|
||||
onClick={() => setApiDrawerOpen(!apiDrawerOpen)}
|
||||
title={apiDrawerOpen ? "关闭API参考" : "打开API参考"}
|
||||
<div className={styles.editorWithApi}>
|
||||
{ApiDrawer()}
|
||||
<div
|
||||
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',
|
||||
|
||||
@@ -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"
|
||||
@@ -2146,4 +2161,4 @@ UnityComponent.propTypes = {
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
export default UnityComponent;
|
||||
export default UnityComponent;
|
||||
|
||||
119
scratch-gui/src/lib/unity-build-loader.js
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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="公众号小图标" />
|
||||
|
||||
4
scratch-gui/src/playground/ai-config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// 给 chunks: [] 的 AI 静态页注入与 login 相同的 config.js
|
||||
import CONFIG from './config.js';
|
||||
|
||||
window.CONFIG = CONFIG;
|
||||
2104
scratch-gui/src/playground/ai-draw.ejs
Normal file
2124
scratch-gui/src/playground/ai-huiben.ejs
Normal file
1988
scratch-gui/src/playground/ai-music.ejs
Normal file
2178
scratch-gui/src/playground/ai-text.ejs
Normal file
1897
scratch-gui/src/playground/ai-video.ejs
Normal file
2482
scratch-gui/src/playground/ai-voice.ejs
Normal file
2516
scratch-gui/src/playground/ai.ejs
Normal file
9
scratch-gui/src/playground/ai.js
Normal 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 是否为最新构建');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
360
scratch-gui/src/playground/coming-soon.ejs
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
@@ -3449,4 +3449,4 @@ function getLanguageName(t) {
|
||||
}
|
||||
|
||||
// 导出函数,以便其他模块可以使用
|
||||
export { getUrlParameter, fetchCompetitionInfo };
|
||||
export { getUrlParameter, fetchCompetitionInfo };
|
||||
|
||||
@@ -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="公众号小图标" />
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
/** 方案 B:nginx / 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' &&
|
||||
|
||||
@@ -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',
|
||||
/** 方案 B:nginx / 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;
|
||||
@@ -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',
|
||||
/** 方案 B:nginx / 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;
|
||||
@@ -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',
|
||||
/** 方案 B:nginx / 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;
|
||||
@@ -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',
|
||||
/** 方案 B:nginx / 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;
|
||||
1765
scratch-gui/src/playground/course-detail.ejs
Normal file
76
scratch-gui/src/playground/devtool-control.js
Normal 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;
|
||||
}
|
||||
@@ -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="公众号小图标" />
|
||||
|
||||
3
scratch-gui/src/playground/home.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { stopDev } from './devtool-control.js';
|
||||
|
||||
window.stopDev = stopDev;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="公众号小图标" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
|
||||
@@ -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="公众号小图标" />
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
1752
scratch-gui/src/playground/works-plaza.ejs
Normal file
294
scratch-gui/static/css/ai-account-modals.css
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
scratch-gui/static/images/about-illustration.svg
Normal file
|
After Width: | Height: | Size: 19 MiB |
19
scratch-gui/static/images/ai-sparkle.svg
Normal 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 |
BIN
scratch-gui/static/images/aigc/ai-banner1.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
scratch-gui/static/images/aigc/ai-banner2.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
scratch-gui/static/images/aigc/ai-banner3.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
scratch-gui/static/images/aigc/ai-fanyi.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
scratch-gui/static/images/aigc/ai-home-page1.png
Normal file
|
After Width: | Height: | Size: 503 KiB |
BIN
scratch-gui/static/images/aigc/ai-icon-class.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
scratch-gui/static/images/aigc/ai-icon-locked.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
scratch-gui/static/images/aigc/ai-icon-lockopen.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
scratch-gui/static/images/aigc/ai-logo.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
scratch-gui/static/images/aigc/ai-wenben.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
scratch-gui/static/images/aigc/ai-zuojia.png
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_01.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_02.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_03.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_04.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_05.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_06.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_07.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_08.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_09.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_10.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_11.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
scratch-gui/static/images/aigc/ai_programming_avatar_12.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
scratch-gui/static/images/aigc/icon-ai-huiben.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
scratch-gui/static/images/aigc/icon-ai-huitu.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
scratch-gui/static/images/aigc/icon-ai-music.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
scratch-gui/static/images/aigc/icon-ai-shiping.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
scratch-gui/static/images/aigc/icon-ai-wenben.png
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
scratch-gui/static/images/aigc/icon-ai-yuying.png
Normal file
|
After Width: | Height: | Size: 137 KiB |