blockly.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. import * as Blockly from "blockly";
  2. import 'blockly/msg/zh-hans';
  3. import {javascriptGenerator} from "blockly/javascript";
  4. import {pythonGenerator} from "blockly/python";
  5. // 导入图片资源
  6. import about_turnImage from '@/assets/images/blockly/component/about_turn.png';
  7. import move_backwardImage from '@/assets/images/blockly/component/move_backward.png';
  8. import move_forwardImage from '@/assets/images/blockly/component/move_forward.png';
  9. import pick_upImage from '@/assets/images/blockly/component/pick_up.png';
  10. import turn_leftImage from '@/assets/images/blockly/component/turn_left.png';
  11. import turn_rightImage from '@/assets/images/blockly/component/turn_right.png';
  12. import useImage from '@/assets/images/blockly/component/use.png';
  13. import pauseImage from '@/assets/images/blockly/component/pause.png';
  14. import play_soundImage from '@/assets/images/blockly/component/sound.png';
  15. import constructImage from '@/assets/images/blockly/component/construct.png';
  16. import lightImage from '@/assets/images/blockly/component/light.png';
  17. import repeatImage from '@/assets/images/blockly/component/repeat.png';
  18. import ifImage from '@/assets/images/blockly/component/if.png';
  19. import ifElseImage from '@/assets/images/blockly/component/if_else.png';
  20. import lightRedImage from '@/assets/images/blockly/light/red.png';
  21. import lightYellowImage from '@/assets/images/blockly/light/yellow.png';
  22. import lightGreenImage from '@/assets/images/blockly/light/green.png';
  23. import {DICT_TYPE} from '@/utils/dictUtils.js';
  24. // 安全地获取和解析字典数据
  25. const getDictDataFromStorage = (dictType) => {
  26. try {
  27. const data = localStorage.getItem(dictType);
  28. return data ? JSON.parse(data) : [];
  29. } catch (error) {
  30. console.error(`解析${dictType}字典数据失败:`, error);
  31. return [];
  32. }
  33. };
  34. // 地图元素类型字典
  35. const savedMapTypeDict = getDictDataFromStorage(DICT_TYPE.AI_BLOCKLY_MAP_TYPE);
  36. const BLOCKLY_MAP_TYPE_DICT = savedMapTypeDict.reduce((acc, item) => {
  37. acc[item.value.toUpperCase()] = item.value;
  38. return acc;
  39. }, {});
  40. // 特殊积木字典
  41. const blocklyMapSpecialDict = getDictDataFromStorage(DICT_TYPE.BLOCKLY_MAP_SPECIAL);
  42. const BLOCKLY_MAP_SPECIAL_DICT = blocklyMapSpecialDict.reduce((acc, item) => {
  43. acc[item.value.toUpperCase()] = item.value;
  44. return acc;
  45. }, {});
  46. // 地图标记字典
  47. const blocklyMapMarkDict = getDictDataFromStorage(DICT_TYPE.BLOCKLY_MAP_MARK);
  48. const BLOCKLY_MAP_MARK_DICT = blocklyMapMarkDict.reduce((acc, item) => {
  49. acc[item.value.toUpperCase()] = {
  50. value: item.value,
  51. label: item.label,
  52. img: item.remark
  53. };
  54. return acc;
  55. }, {});
  56. // 灯光颜色字典
  57. const BLOCKLY_LIGHT_COLOR_DICT = [
  58. {
  59. label: '红色',
  60. color: [233, 77, 88],
  61. img: lightRedImage,
  62. },
  63. {
  64. label: '黄色',
  65. color: [233, 211, 36],
  66. img: lightYellowImage,
  67. },
  68. {
  69. label: '绿色',
  70. color: [118, 187, 45],
  71. img: lightGreenImage,
  72. },
  73. ];
  74. /**
  75. * 定义所有可用的自定义积木配置
  76. */
  77. const availableBlocks = {
  78. // 通用积木 - 始终可用(移动控制)
  79. move_forward: {
  80. jsonConfig: {
  81. "type": "move_forward",
  82. "message0": "%1 向前移动",
  83. "args0": [
  84. {
  85. "type": "field_image",
  86. "src": move_forwardImage,
  87. "width": 20,
  88. "height": 20,
  89. "alt": "向前移动"
  90. }
  91. ],
  92. "previousStatement": null,
  93. "nextStatement": null,
  94. "colour": 230,
  95. "tooltip": "控制角色向前移动一格",
  96. "helpUrl": ""
  97. },
  98. isGeneral: true // 标记为通用积木
  99. },
  100. move_forward_param: {
  101. jsonConfig: {
  102. "type": "move_forward_param",
  103. "message0": "%1 向前移动 %2 次",
  104. "args0": [
  105. {
  106. "type": "field_image",
  107. "src": move_forwardImage,
  108. "width": 20,
  109. "height": 20,
  110. "alt": "向前移动多次"
  111. },
  112. {
  113. "type": "field_number",
  114. "name": "PARAM",
  115. "value": 1,
  116. "min": 1,
  117. "max": 10,
  118. "precision": 1
  119. }
  120. ],
  121. "previousStatement": null,
  122. "nextStatement": null,
  123. "colour": 230,
  124. "tooltip": "控制角色向前移动指定次数",
  125. "helpUrl": ""
  126. },
  127. isGeneral: true // 标记为通用积木
  128. },
  129. move_backward: {
  130. jsonConfig: {
  131. "type": "move_backward",
  132. "message0": "%1 向后移动",
  133. "args0": [
  134. {
  135. "type": "field_image",
  136. "src": move_backwardImage,
  137. "width": 20,
  138. "height": 20,
  139. "alt": "向后移动"
  140. }
  141. ],
  142. "previousStatement": null,
  143. "nextStatement": null,
  144. "colour": 230,
  145. "tooltip": "控制角色向后移动一格",
  146. "helpUrl": ""
  147. },
  148. isGeneral: true
  149. },
  150. move_backward_param: {
  151. jsonConfig: {
  152. "type": "move_backward_param",
  153. "message0": "%1 向后移动 %2 次",
  154. "args0": [
  155. {
  156. "type": "field_image",
  157. "src": move_backwardImage,
  158. "width": 20,
  159. "height": 20,
  160. "alt": "向后移动多次"
  161. },
  162. {
  163. "type": "field_number",
  164. "name": "PARAM",
  165. "value": 1,
  166. "min": 1,
  167. "max": 10,
  168. "precision": 1
  169. }
  170. ],
  171. "previousStatement": null,
  172. "nextStatement": null,
  173. "colour": 230,
  174. "tooltip": "控制角色向后移动指定次数",
  175. "helpUrl": ""
  176. },
  177. isGeneral: true
  178. },
  179. turn_left: {
  180. jsonConfig: {
  181. "type": "turn_left",
  182. "message0": "%1 向左转",
  183. "args0": [
  184. {
  185. "type": "field_image",
  186. "src": turn_leftImage,
  187. "width": 20,
  188. "height": 20,
  189. "alt": "向左转"
  190. }
  191. ],
  192. "previousStatement": null,
  193. "nextStatement": null,
  194. "colour": 230,
  195. "tooltip": "控制角色向左转",
  196. "helpUrl": ""
  197. },
  198. isGeneral: true
  199. },
  200. turn_right: {
  201. jsonConfig: {
  202. "type": "turn_right",
  203. "message0": "%1 向右转",
  204. "args0": [
  205. {
  206. "type": "field_image",
  207. "src": turn_rightImage,
  208. "width": 20,
  209. "height": 20,
  210. "alt": "向右转"
  211. }
  212. ],
  213. "previousStatement": null,
  214. "nextStatement": null,
  215. "colour": 230,
  216. "tooltip": "控制角色向右转",
  217. "helpUrl": ""
  218. },
  219. isGeneral: true
  220. },
  221. turn_around: {
  222. jsonConfig: {
  223. "type": "turn_around",
  224. "message0": "%1 向后转",
  225. "args0": [
  226. {
  227. "type": "field_image",
  228. "src": about_turnImage,
  229. "width": 20,
  230. "height": 20,
  231. "alt": "向后转"
  232. }
  233. ],
  234. "previousStatement": null,
  235. "nextStatement": null,
  236. "colour": 230,
  237. "tooltip": "控制角色向后转",
  238. "helpUrl": ""
  239. },
  240. isGeneral: true
  241. },
  242. // 通用积木 - 始终可用(功能积木)
  243. pickup_item: {
  244. jsonConfig: {
  245. "type": "pickup_item",
  246. "message0": "%1 拾取物品",
  247. "args0": [
  248. {
  249. "type": "field_image",
  250. "src": pick_upImage,
  251. "width": 20,
  252. "height": 20,
  253. "alt": "拾取物品"
  254. }
  255. ],
  256. "previousStatement": null,
  257. "nextStatement": null,
  258. "colour": 30,
  259. "tooltip": "尝试拾取当前位置的物品",
  260. "helpUrl": ""
  261. },
  262. isGeneral: true
  263. },
  264. use_item: {
  265. jsonConfig: {
  266. "type": "use_item",
  267. "message0": "%1 使用物品",
  268. "args0": [
  269. {
  270. "type": "field_image",
  271. "src": useImage,
  272. "width": 20,
  273. "height": 20,
  274. "alt": "使用物品"
  275. }
  276. ],
  277. "previousStatement": null,
  278. "nextStatement": null,
  279. "colour": 30,
  280. "tooltip": "在当前位置使用物品",
  281. "helpUrl": ""
  282. },
  283. isGeneral: true
  284. },
  285. // 逻辑积木 - 自定义如果否则带图标
  286. controls_if: {
  287. jsonConfig: {
  288. "type": "controls_if",
  289. "message0": "%1 如果 %2",
  290. "args0": [
  291. {
  292. "type": "field_image",
  293. "src": ifImage,
  294. "width": 20,
  295. "height": 20,
  296. "alt": "条件"
  297. },
  298. {
  299. "type": "input_value",
  300. "name": "IF0",
  301. "check": "Boolean"
  302. }
  303. ],
  304. "message1": "执行%1",
  305. "args1": [
  306. {
  307. "type": "input_statement",
  308. "name": "DO0"
  309. }
  310. ],
  311. "previousStatement": null,
  312. "nextStatement": null,
  313. "colour": 210,
  314. "tooltip": "如果条件为真,则执行相应的代码。",
  315. "helpUrl": "",
  316. "mutator": "controls_if_mutator"
  317. },
  318. isGeneral: true // 标记为通用积木
  319. },
  320. // 循环积木 - 自定义重复执行带图标
  321. controls_repeat_ext: {
  322. jsonConfig: {
  323. "type": "controls_repeat_ext",
  324. "message0": "%1 重复 %2 次",
  325. "args0": [
  326. {
  327. "type": "field_image",
  328. "src": repeatImage,
  329. "width": 25,
  330. "height": 25,
  331. "alt": "循环"
  332. },
  333. {
  334. "type": "field_number",
  335. "name": "TIMES",
  336. "value": 1,
  337. "min": 1,
  338. "max": 100
  339. }
  340. ],
  341. "message1": "执行 %1",
  342. "args1": [
  343. {
  344. "type": "input_statement",
  345. "name": "DO"
  346. }
  347. ],
  348. "previousStatement": null,
  349. "nextStatement": null,
  350. "colour": "#5BA55B",
  351. "tooltip": "重复执行内部代码指定次数",
  352. "helpUrl": ""
  353. },
  354. isGeneral: true // 标记为通用积木
  355. },
  356. // 当经过某个形状的积木(可作为参数使用的value类型)
  357. when_passed: {
  358. jsonConfig: {
  359. "type": "when_passed",
  360. "message0": "当经过 : %1",
  361. "args0": [
  362. {
  363. "type": "field_dropdown",
  364. "name": "SHAPE",
  365. "options": Object.values(BLOCKLY_MAP_MARK_DICT).map(shape => [
  366. {
  367. "src": shape.img,
  368. "width": 30,
  369. "height": 30,
  370. "alt": shape.label
  371. },
  372. shape.value
  373. ])
  374. }
  375. ],
  376. "output": "Boolean",
  377. "colour": 30,
  378. "tooltip": "检查角色是否经过指定形状,可作为参数使用",
  379. "helpUrl": ""
  380. },
  381. isGeneral: true
  382. },
  383. // 特殊积木 - 可根据配置动态加载
  384. pause: {
  385. jsonConfig: {
  386. "type": "pause",
  387. "message0": "%1 暂停 %2 秒",
  388. "args0": [
  389. {
  390. "type": "field_image",
  391. "src": pauseImage,
  392. "width": 20,
  393. "height": 20,
  394. "alt": "暂停"
  395. },
  396. {
  397. "type": "field_number",
  398. "name": "SECONDS",
  399. "value": 1,
  400. "min": 1,
  401. "max": 10,
  402. "precision": 1
  403. }
  404. ],
  405. "previousStatement": null,
  406. "nextStatement": null,
  407. "colour": 0,
  408. "tooltip": "让角色在原地停留指定的秒数",
  409. "helpUrl": ""
  410. },
  411. isGeneral: false
  412. },
  413. play_sound: {
  414. jsonConfig: {
  415. "type": "play_sound",
  416. "message0": "%1 声音: %2",
  417. "args0": [
  418. {
  419. "type": "field_image",
  420. "src": play_soundImage,
  421. "width": 20,
  422. "height": 20,
  423. "alt": "声音"
  424. },
  425. {
  426. "type": "field_dropdown",
  427. "name": "SOUND",
  428. "options": [
  429. ["打招呼", "打招呼"],
  430. ]
  431. }
  432. ],
  433. "previousStatement": null,
  434. "nextStatement": null,
  435. "colour": 160,
  436. "tooltip": "播放指定的声音特效",
  437. "helpUrl": ""
  438. },
  439. isGeneral: false
  440. },
  441. construct: {
  442. jsonConfig: {
  443. "type": "construct",
  444. "message0": "%1 修建",
  445. "args0": [
  446. {
  447. "type": "field_image",
  448. "src": constructImage,
  449. "width": 20,
  450. "height": 20,
  451. "alt": "修建"
  452. }
  453. ],
  454. "previousStatement": null,
  455. "nextStatement": null,
  456. "colour": 160,
  457. "tooltip": "修建处理",
  458. "helpUrl": ""
  459. },
  460. isGeneral: false
  461. },
  462. light: {
  463. jsonConfig: {
  464. "type": "light",
  465. "message0": "%1 灯光 %2",
  466. "args0": [
  467. {
  468. "type": "field_image",
  469. "src": lightImage,
  470. "width": 25,
  471. "height": 25,
  472. "alt": "灯光"
  473. },
  474. {
  475. "type": "field_dropdown",
  476. "name": "COLOR",
  477. "options": BLOCKLY_LIGHT_COLOR_DICT.map(item => [
  478. {
  479. "src": item.img,
  480. "width": 20,
  481. "height": 20,
  482. "alt": item.label
  483. },
  484. item.label
  485. ])
  486. }
  487. ],
  488. "previousStatement": null,
  489. "nextStatement": null,
  490. "colour": 30,
  491. "tooltip": "在当前位置产生灯光效果",
  492. "helpUrl": ""
  493. },
  494. isGeneral: false
  495. }
  496. };
  497. /**
  498. * 定义所有可用的JavaScript生成器配置
  499. */
  500. const availableGenerators = {
  501. // 通用生成器 - 始终可用
  502. move_forward: function(block) {
  503. return 'await moveForward();\n';
  504. },
  505. move_forward_param: function(block) {
  506. const stepCount = block.getFieldValue('PARAM');
  507. return `await moveForward(${stepCount});\n`;
  508. },
  509. move_backward: function(block) {
  510. return 'await moveBackward();\n';
  511. },
  512. move_backward_param: function(block) {
  513. const stepCount = block.getFieldValue('PARAM');
  514. return `await moveBackward(${stepCount});\n`;
  515. },
  516. turn_left: function(block) {
  517. return 'await turnLeft();\n';
  518. },
  519. turn_right: function(block) {
  520. return 'await turnRight();\n';
  521. },
  522. turn_around: function(block) {
  523. return 'await turnAround();\n';
  524. },
  525. pickup_item: function(block) {
  526. return 'await pickupItem();\n';
  527. },
  528. use_item: function(block) {
  529. return 'await useItem();\n';
  530. },
  531. when_passed: function(block) {
  532. const shape = block.getFieldValue('SHAPE');
  533. return [`whenPassed('${shape}')`, javascriptGenerator.ORDER_FUNCTION_CALL];
  534. },
  535. // 条件判断生成器 - 使用Blockly默认实现方式确保兼容性
  536. controls_if: function(block) {
  537. let code = '';
  538. let branchCode, conditionCode;
  539. let n = 0;
  540. // 生成if条件
  541. if (block.getInput('IF0')) {
  542. conditionCode = javascriptGenerator.valueToCode(block, 'IF0', javascriptGenerator.ORDER_NONE) || 'false';
  543. branchCode = javascriptGenerator.statementToCode(block, 'DO0');
  544. code += `if (${conditionCode}) {
  545. ${javascriptGenerator.prefixLines(branchCode, javascriptGenerator.INDENT)}}
  546. `;
  547. n = 1;
  548. }
  549. // 生成else if条件
  550. for (; block.getInput('IF' + n); n++) {
  551. conditionCode = javascriptGenerator.valueToCode(block, 'IF' + n, javascriptGenerator.ORDER_NONE) || 'false';
  552. branchCode = javascriptGenerator.statementToCode(block, 'DO' + n);
  553. code += `else if (${conditionCode}) {
  554. ${javascriptGenerator.prefixLines(branchCode, javascriptGenerator.INDENT)}}
  555. `;
  556. }
  557. // 生成else条件
  558. if (block.getInput('ELSE')) {
  559. const elseCode = javascriptGenerator.statementToCode(block, 'ELSE');
  560. code += `else {
  561. ${javascriptGenerator.prefixLines(elseCode, javascriptGenerator.INDENT)}}
  562. `;
  563. }
  564. return code;
  565. },
  566. controls_repeat_ext: function(block) {
  567. const repeats = block.getFieldValue('TIMES');
  568. const branch = javascriptGenerator.statementToCode(block, 'DO');
  569. const code = `for (let i = 0; i < ${repeats}; i++) {\n${javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT)}}\n`;
  570. return code;
  571. },
  572. // 特殊生成器 - 可根据配置动态加载
  573. pause: function(block) {
  574. const seconds = block.getFieldValue('SECONDS');
  575. return `await pause(${seconds});\n`;
  576. },
  577. play_sound: function(block) {
  578. const sound = block.getFieldValue('SOUND');
  579. return `await playSound('${sound}');
  580. `;
  581. },
  582. construct: function(block) {
  583. return 'await construct();\n';
  584. },
  585. light: function(block) {
  586. const color = block.getFieldValue('COLOR');
  587. return `await light('${color}');
  588. `;
  589. }
  590. };
  591. /**
  592. * 定义所有可用的Python生成器配置
  593. */
  594. const availablePythonGenerators = {
  595. // 通用生成器 - 始终可用
  596. move_forward: function(block) {
  597. return 'move_forward()\n';
  598. },
  599. move_forward_param: function(block) {
  600. const stepCount = block.getFieldValue('PARAM');
  601. return `move_forward(${stepCount})\n`;
  602. },
  603. move_backward: function(block) {
  604. return 'move_backward()\n';
  605. },
  606. move_backward_param: function(block) {
  607. const stepCount = block.getFieldValue('PARAM');
  608. return `move_backward(${stepCount})\n`;
  609. },
  610. turn_left: function(block) {
  611. return 'turn_left()\n';
  612. },
  613. turn_right: function(block) {
  614. return 'turn_right()\n';
  615. },
  616. turn_around: function(block) {
  617. return 'turn_around()\n';
  618. },
  619. pickup_item: function(block) {
  620. return 'pickup_item()\n';
  621. },
  622. use_item: function(block) {
  623. return 'use_item()\n';
  624. },
  625. when_passed: function(block) {
  626. const shape = block.getFieldValue('SHAPE');
  627. return [`when_passed('${shape}')`, pythonGenerator.ORDER_FUNCTION_CALL];
  628. },
  629. // 条件判断生成器
  630. controls_if: function(block) {
  631. const n = block.elseifCount_;
  632. let code = '';
  633. let branchCode = '';
  634. let branchCondition = '';
  635. // 生成if条件
  636. branchCondition = pythonGenerator.valueToCode(block, 'IF0', pythonGenerator.ORDER_NONE) || 'False';
  637. branchCode = pythonGenerator.statementToCode(block, 'DO0');
  638. code += `if ${branchCondition}:
  639. ${pythonGenerator.prefixLines(branchCode, pythonGenerator.INDENT)}
  640. `;
  641. // 生成else if条件
  642. for (let i = 1; i <= n; i++) {
  643. branchCondition = pythonGenerator.valueToCode(block, 'IF' + i, pythonGenerator.ORDER_NONE) || 'False';
  644. branchCode = pythonGenerator.statementToCode(block, 'DO' + i);
  645. code += `elif ${branchCondition}:
  646. ${pythonGenerator.prefixLines(branchCode, pythonGenerator.INDENT)}
  647. `;
  648. }
  649. debugger
  650. // 生成else条件
  651. const elseCode = pythonGenerator.statementToCode(block, 'ELSE');
  652. if (elseCode) {
  653. code += `else:
  654. ${pythonGenerator.prefixLines(elseCode, pythonGenerator.INDENT)}
  655. `;
  656. }
  657. return code;
  658. },
  659. controls_repeat_ext: function(block) {
  660. const repeats = block.getFieldValue('TIMES');
  661. const branch = pythonGenerator.statementToCode(block, 'DO');
  662. const code = `for i in range(${repeats}):\n${pythonGenerator.prefixLines(branch, pythonGenerator.INDENT)}\n`;
  663. return code;
  664. },
  665. // 特殊生成器 - 可根据配置动态加载
  666. pause: function(block) {
  667. const seconds = block.getFieldValue('SECONDS');
  668. return `pause(${seconds})\n`;
  669. },
  670. play_sound: function(block) {
  671. const sound = block.getFieldValue('SOUND');
  672. return `play_sound('${sound}')
  673. `;
  674. },
  675. construct: function(block) {
  676. return 'construct()\n';
  677. },
  678. light: function(block) {
  679. const color = block.getFieldValue('COLOR');
  680. return `light('${color}')
  681. `;
  682. }
  683. };
  684. /**
  685. * 注册自定义Blockly积木
  686. * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
  687. */
  688. export function registerCustomBlocks(blocklySpecialBlocks = []) {
  689. // 确保blocklySpecialBlocks始终是数组
  690. blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
  691. // 遍历所有可用积木
  692. for (const [blockName, blockConfig] of Object.entries(availableBlocks)) {
  693. // 如果是通用积木,或者是允许的特殊积木,则注册
  694. if (blockConfig.isGeneral || blocklySpecialBlocks.includes(blockName)) {
  695. Blockly.Blocks[blockName] = {
  696. init: function() {
  697. this.jsonInit(blockConfig.jsonConfig);
  698. }
  699. };
  700. }
  701. }
  702. }
  703. /**
  704. * 设置Blockly中文本地化
  705. */
  706. export function setupBlocklyChineseLocale() {
  707. // 使用扩展方式覆盖默认的英文文本为中文
  708. const locale = {
  709. // 逻辑积木中文配置
  710. CONTROLS_IF_MSG_IF: "如果",
  711. CONTROLS_IF_MSG_ELSEIF: "否则如果",
  712. CONTROLS_IF_MSG_ELSE: "否则",
  713. CONTROLS_IF_MSG_THEN: "执行",
  714. CONTROLS_IF_MSG_DO: "执行",
  715. CONTROLS_IF_TOOLTIP_1: "如果条件为真,则执行相应的代码。",
  716. CONTROLS_IF_TOOLTIP_2: "如果条件为真,则执行相应的代码;否则执行其他代码。",
  717. CONTROLS_IF_TOOLTIP_3: "如果条件为真,则执行相应的代码;否则检查其他条件。",
  718. CONTROLS_IF_TOOLTIP_4: "如果条件为真,则执行相应的代码;否则检查其他条件,如果都不满足则执行最后代码。",
  719. // 控制块的弹出菜单文本
  720. CONTROLS_IF_IF: "如果",
  721. CONTROLS_IF_ELSEIF: "否则如果",
  722. CONTROLS_IF_ELSE: "否则",
  723. CONTROLS_IF_IF_TITLE: "如果",
  724. CONTROLS_IF_IF_TITLE_IF: "如果",
  725. CONTROLS_IF_ELSEIF_TITLE_ELSEIF: "否则如果",
  726. CONTROLS_IF_ELSE_TITLE: "否则",
  727. CONTROLS_IF_ELSE_TITLE_ELSE: "否则",
  728. // 条件块的提示文本和帮助URL
  729. CONTROLS_IF_HELPURL: "条件语句帮助",
  730. CONTROLS_IF_TOOLTIP_IF: "添加或删除条件。",
  731. CONTROLS_IF_TOOLTIP_ELSE: "添加或删除否则块。",
  732. // 添加设置按钮相关的中文配置
  733. CONTROLS_IF_ELSEIF_TOOLTIP: "否则如果条件为真,则执行相应的代码。",
  734. CONTROLS_IF_ELSE_TOOLTIP: "否则执行相应的代码。",
  735. // 比较运算符中文配置
  736. LOGIC_COMPARE_EQ: "等于",
  737. LOGIC_COMPARE_NEQ: "不等于",
  738. LOGIC_COMPARE_LT: "小于",
  739. LOGIC_COMPARE_LTE: "小于等于",
  740. LOGIC_COMPARE_GT: "大于",
  741. LOGIC_COMPARE_GTE: "大于等于",
  742. LOGIC_COMPARE_TOOLTIP_EQ: "比较两个值是否相等。",
  743. LOGIC_COMPARE_TOOLTIP_NEQ: "比较两个值是否不相等。",
  744. LOGIC_COMPARE_TOOLTIP_LT: "比较第一个值是否小于第二个值。",
  745. LOGIC_COMPARE_TOOLTIP_LTE: "比较第一个值是否小于或等于第二个值。",
  746. LOGIC_COMPARE_TOOLTIP_GT: "比较第一个值是否大于第二个值。",
  747. LOGIC_COMPARE_TOOLTIP_GTE: "比较第一个值是否大于或等于第二个值。",
  748. // 逻辑运算中文配置
  749. LOGIC_OPERATION_AND: "且",
  750. LOGIC_OPERATION_OR: "或",
  751. LOGIC_OPERATION_TOOLTIP_AND: "如果两个条件都为真,则结果为真。",
  752. LOGIC_OPERATION_TOOLTIP_OR: "如果任一条件为真,则结果为真。",
  753. // 布尔值配置
  754. LOGIC_BOOLEAN_TRUE: "真",
  755. LOGIC_BOOLEAN_FALSE: "假",
  756. LOGIC_BOOLEAN_TOOLTIP: "返回一个布尔值:真或假。",
  757. // 循环积木中文配置
  758. CONTROLS_REPEAT_TITLE: "重复执行%1次",
  759. CONTROLS_REPEAT_TOOLTIP: "重复执行内部代码指定次数。",
  760. CONTROLS_REPEAT_MSG_DO: "执行",
  761. CONTROLS_REPEAT_INPUT_DO: "执行",
  762. CONTROLS_WHILEUNTIL_MSG_DO: "执行",
  763. CONTROLS_WHILEUNTIL_INPUT_DO: "执行",
  764. CONTROLS_FOR_MSG_DO: "执行",
  765. CONTROLS_FOR_INPUT_DO: "执行",
  766. CONTROLS_FOR_EACH_MSG_DO: "执行",
  767. CONTROLS_FOR_EACH_INPUT_DO: "执行",
  768. // 数学积木中文配置
  769. MATH_NUMBER_TOOLTIP: "一个数字。在编辑器中双击以更改。",
  770. MATH_ADDITION_SYMBOL: "加",
  771. MATH_SUBTRACTION_SYMBOL: "减",
  772. MATH_MULTIPLICATION_SYMBOL: "乘",
  773. MATH_DIVISION_SYMBOL: "除",
  774. MATH_ARITHMETIC_TOOLTIP_ADD: "返回两个数的和。",
  775. MATH_ARITHMETIC_TOOLTIP_SUBTRACT: "返回第一个数减去第二个数的差。",
  776. MATH_ARITHMETIC_TOOLTIP_MULTIPLY: "返回两个数的积。",
  777. MATH_ARITHMETIC_TOOLTIP_DIVIDE: "返回第一个数除以第二个数的商。"
  778. };
  779. // 使用Object.assign来合并配置,避免直接修改导入对象
  780. Object.assign(Blockly.Msg, locale);
  781. }
  782. /**
  783. * 注册JavaScript生成器
  784. * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
  785. */
  786. export function registerJavaScriptGenerators(blocklySpecialBlocks = []) {
  787. // 确保blocklySpecialBlocks始终是数组
  788. blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
  789. // 遍历所有可用生成器
  790. for (const [blockName, generatorFunc] of Object.entries(availableGenerators)) {
  791. // 如果是通用生成器,或者是允许的特殊生成器对应的生成器,则注册
  792. if (availableBlocks[blockName].isGeneral || blocklySpecialBlocks.includes(blockName)) {
  793. javascriptGenerator.forBlock[blockName] = generatorFunc;
  794. }
  795. }
  796. // 为text_print块添加生成器,用于调试(始终可用)
  797. javascriptGenerator.forBlock['text_print'] = function(block) {
  798. const msg = javascriptGenerator.valueToCode(block, 'TEXT', javascriptGenerator.ORDER_NONE) || '';
  799. return msg;
  800. };
  801. }
  802. /**
  803. * 注册Python生成器
  804. * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
  805. */
  806. export function registerPythonGenerators(blocklySpecialBlocks = []) {
  807. // 确保blocklySpecialBlocks始终是数组
  808. blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
  809. // 遍历所有可用生成器
  810. for (const [blockName, generatorFunc] of Object.entries(availablePythonGenerators)) {
  811. // 如果是通用生成器,或者是允许的特殊生成器对应的生成器,则注册
  812. if (availableBlocks[blockName].isGeneral || blocklySpecialBlocks.includes(blockName)) {
  813. pythonGenerator.forBlock[blockName] = generatorFunc;
  814. }
  815. }
  816. // 为text_print块添加生成器,用于调试(始终可用)
  817. pythonGenerator.forBlock['text_print'] = function(block) {
  818. const msg = pythonGenerator.valueToCode(block, 'TEXT', pythonGenerator.ORDER_NONE) || '';
  819. return msg;
  820. };
  821. }
  822. /**
  823. * 初始化Blockly工作区
  824. * @param {HTMLElement} blocklyDiv - Blockly容器元素
  825. * @param {HTMLElement} toolbox - 工具箱元素
  826. * @returns {Blockly.WorkspaceSvg} - Blockly工作区实例
  827. */
  828. export function initBlockly(options = {}) {
  829. // 应用中文配置
  830. setupBlocklyChineseLocale();
  831. const defaultOptions = {
  832. toolbox: document.getElementById('toolbox'),
  833. collapse: false,
  834. comments: true,
  835. disable: false, // 设为false以允许编辑
  836. maxBlocks: Infinity,
  837. trashcan: true,
  838. horizontalLayout: false,
  839. toolboxPosition: 'start',
  840. toolboxCollapse: false, // 设置工具箱默认展开
  841. toolboxAlwaysExpanded: true, // 确保点击类别后保持展开状态
  842. css: true,
  843. media: 'https://unpkg.com/blockly/media/',
  844. rtl: false,
  845. scrollbars: true,
  846. sounds: false, // 禁用声音以提高性能
  847. oneBasedIndex: true,
  848. grid: {
  849. spacing: 20,
  850. length: 3,
  851. colour: "#ccc",
  852. snap: true
  853. },
  854. zoom: {
  855. controls: true,
  856. wheel: true,
  857. startScale: 1.0,
  858. maxScale: 3,
  859. minScale: 0.3,
  860. scaleSpeed: 1.2
  861. }
  862. };
  863. // 合并默认选项和用户提供的选项
  864. const finalOptions = { ...defaultOptions, ...options };
  865. return Blockly.inject('blocklyDiv', finalOptions);
  866. }
  867. /**
  868. * 获取所有可用的积木配置
  869. * @returns {Object} 所有可用积木的配置对象
  870. */
  871. export function getAllBlocksConfig() {
  872. return {...availableBlocks};
  873. }
  874. /**
  875. * 获取所有可用的JavaScript生成器配置
  876. * @returns {Object} 所有可用JavaScript生成器的配置对象
  877. */
  878. export function getAllGeneratorsConfig() {
  879. return {...availableGenerators};
  880. }
  881. /**
  882. * 获取所有可用的Python生成器配置
  883. * @returns {Object} 所有可用Python生成器的配置对象
  884. */
  885. export function getAllPythonGeneratorsConfig() {
  886. return {...availablePythonGenerators};
  887. }
  888. // 导出字典常量
  889. export { BLOCKLY_MAP_TYPE_DICT, BLOCKLY_MAP_MARK_DICT, BLOCKLY_MAP_SPECIAL_DICT,
  890. BLOCKLY_LIGHT_COLOR_DICT };
  891. export default {
  892. registerCustomBlocks,
  893. registerJavaScriptGenerators,
  894. registerPythonGenerators,
  895. setupBlocklyChineseLocale,
  896. initBlockly,
  897. getAllBlocksConfig,
  898. getAllGeneratorsConfig,
  899. BLOCKLY_MAP_TYPE_DICT,
  900. BLOCKLY_MAP_MARK_DICT,
  901. BLOCKLY_MAP_SPECIAL_DICT
  902. };