Quellcode durchsuchen

1、blockly编程组件优化
2、加入动态积木组件根据后端配置显示
3、不同类型课程都使用同一字段courseContent

liyanbo vor 4 Monaten
Ursprung
Commit
fd9ed91e4f

+ 401 - 0
src/api/blockly/blockly.js

@@ -0,0 +1,401 @@
+import * as Blockly from "blockly";
+import 'blockly/msg/zh-hans';
+import { javascriptGenerator } from "blockly/javascript";
+
+/**
+ * 定义所有可用的自定义积木配置
+ */
+const availableBlocks = {
+  // 通用积木 - 始终可用
+  move_forward: {
+    jsonConfig: {
+      "type": "move_forward",
+      "message0": "向前移动",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 230,
+      "tooltip": "控制角色向前移动一格",
+      "helpUrl": "",
+      "icon": {
+        "src": "▶",
+        "width": 16,
+        "height": 16,
+        "alt": "向前移动"
+      }
+    },
+    isGeneral: true // 标记为通用积木
+  },
+  turn_left: {
+    jsonConfig: {
+      "type": "turn_left",
+      "message0": "向左转",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 230,
+      "tooltip": "控制角色向左转",
+      "helpUrl": "",
+      "icon": {
+        "src": "◀",
+        "width": 16,
+        "height": 16,
+        "alt": "向左转"
+      }
+    },
+    isGeneral: true
+  },
+  turn_right: {
+    jsonConfig: {
+      "type": "turn_right",
+      "message0": "向右转",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 230,
+      "tooltip": "控制角色向右转",
+      "helpUrl": "",
+      "icon": {
+        "src": "▶",
+        "width": 16,
+        "height": 16,
+        "alt": "向右转"
+      }
+    },
+    isGeneral: true
+  },
+  turn_around: {
+    jsonConfig: {
+      "type": "turn_around",
+      "message0": "向后转",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 230,
+      "tooltip": "控制角色向后转",
+      "helpUrl": "",
+      "icon": {
+        "src": "◀▶",
+        "width": 16,
+        "height": 16,
+        "alt": "向后转"
+      }
+    },
+    isGeneral: true
+  },
+  pickup_item: {
+    jsonConfig: {
+      "type": "pickup_item",
+      "message0": "拾取物品",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 30,
+      "tooltip": "尝试拾取当前位置的物品",
+      "helpUrl": "",
+      "icon": {
+        "src": "👆",
+        "width": 16,
+        "height": 16,
+        "alt": "拾取物品"
+      }
+    },
+    isGeneral: true
+  },
+  use_item: {
+    jsonConfig: {
+      "type": "use_item",
+      "message0": "使用物品",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 30,
+      "tooltip": "在当前位置使用物品",
+      "helpUrl": "",
+      "icon": {
+        "src": "👇",
+        "width": 16,
+        "height": 16,
+        "alt": "使用物品"
+      }
+    },
+    isGeneral: true
+  },
+  // 特殊积木 - 可根据配置动态加载
+  pause: {
+    jsonConfig: {
+      "type": "pause",
+      "message0": "暂停 %1 秒",
+      "args0": [
+        {
+          "type": "field_number",
+          "name": "SECONDS",
+          "value": 1,
+          "min": 1,
+          "max": 10,
+          "precision": 1
+        }
+      ],
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 0,
+      "tooltip": "让角色在原地停留指定的秒数",
+      "helpUrl": "",
+      "icon": {
+        "src": "⏸",
+        "width": 16,
+        "height": 16,
+        "alt": "暂停"
+      }
+    },
+    isGeneral: false
+  },
+  play_sound: {
+    jsonConfig: {
+      "type": "play_sound",
+      "message0": "声音",
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 160,
+      "tooltip": "自动触发特效提示音,并根据当前方格类型执行相应操作",
+      "helpUrl": "",
+      "icon": {
+        "src": "🔊",
+        "width": 16,
+        "height": 16,
+        "alt": "声音"
+      }
+    },
+    isGeneral: false
+  }
+};
+
+/**
+ * 定义所有可用的JavaScript生成器配置
+ */
+const availableGenerators = {
+  // 通用生成器 - 始终可用
+  move_forward: function(block) {
+    return 'await moveForward();\n';
+  },
+  turn_left: function(block) {
+    return 'await turnLeft();\n';
+  },
+  turn_right: function(block) {
+    return 'await turnRight();\n';
+  },
+  turn_around: function(block) {
+    return 'await turnAround();\n';
+  },
+  pickup_item: function(block) {
+    return 'await pickupItem();\n';
+  },
+  use_item: function(block) {
+    return 'await useItem();\n';
+  },
+  // 特殊生成器 - 可根据配置动态加载
+  pause: function(block) {
+    const seconds = block.getFieldValue('SECONDS');
+    return `await pause(${seconds});\n`;
+  },
+  play_sound: function(block) {
+    return 'await playSound();\n';
+  }
+};
+
+/**
+ * 注册自定义Blockly积木
+ * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
+ */
+export function registerCustomBlocks(blocklySpecialBlocks = []) {
+  // 遍历所有可用积木
+  for (const [blockName, blockConfig] of Object.entries(availableBlocks)) {
+    // 如果是通用积木,或者是允许的特殊积木,则注册
+    if (blockConfig.isGeneral || blocklySpecialBlocks.includes(blockName)) {
+      Blockly.Blocks[blockName] = {
+        init: function() {
+          this.jsonInit(blockConfig.jsonConfig);
+        }
+      };
+    }
+  }
+}
+
+/**
+ * 设置Blockly中文本地化
+ */
+export function setupBlocklyChineseLocale() {
+  // 覆盖默认的英文文本为中文
+  if (!Blockly.Msg) Blockly.Msg = {};
+
+  // 逻辑积木中文配置
+  Blockly.Msg.CONTROLS_IF_MSG_IF = "如果";
+  Blockly.Msg.CONTROLS_IF_MSG_ELSEIF = "否则如果";
+  Blockly.Msg.CONTROLS_IF_MSG_ELSE = "否则";
+  Blockly.Msg.CONTROLS_IF_MSG_DO = "执行"; // 添加这行将'do'转换为中文
+  Blockly.Msg.CONTROLS_IF_TOOLTIP_1 = "如果条件为真,则执行相应的代码。";
+  Blockly.Msg.CONTROLS_IF_TOOLTIP_2 = "如果条件为真,则执行相应的代码;否则执行其他代码。";
+  Blockly.Msg.CONTROLS_IF_TOOLTIP_3 = "如果条件为真,则执行相应的代码;否则检查其他条件。";
+
+  // 添加设置按钮相关的中文配置
+  Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP = "否则如果条件为真,则执行相应的代码。";
+  Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP = "否则执行相应的代码。";
+
+  // 打开/关闭块的提示文本
+  Blockly.Msg.CONTROLS_IF_TOOLTIP_IF = "添加或删除条件。";
+  Blockly.Msg.CONTROLS_IF_TOOLTIP_ELSE = "添加或删除否则块。";
+
+
+  // 比较运算符中文配置 - 添加显示文本
+  Blockly.Msg.LOGIC_COMPARE_EQ = "等于";
+  Blockly.Msg.LOGIC_COMPARE_NEQ = "不等于";
+  Blockly.Msg.LOGIC_COMPARE_LT = "小于";
+  Blockly.Msg.LOGIC_COMPARE_LTE = "小于等于";
+  Blockly.Msg.LOGIC_COMPARE_GT = "大于";
+  Blockly.Msg.LOGIC_COMPARE_GTE = "大于等于";
+
+  // 比较运算符提示文本(已存在)
+  Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ = "比较两个值是否相等。";
+  Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ = "比较两个值是否不相等。";
+  Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT = "比较第一个值是否小于第二个值。";
+  Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE = "比较第一个值是否小于或等于第二个值。";
+  Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT = "比较第一个值是否大于第二个值。";
+  Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE = "比较第一个值是否大于或等于第二个值。";
+
+  // 逻辑运算中文配置 - 添加显示文本
+  Blockly.Msg.LOGIC_OPERATION_AND = "且";
+  Blockly.Msg.LOGIC_OPERATION_OR = "或";
+
+  // 逻辑运算提示文本(已存在)
+  Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND = "如果两个条件都为真,则结果为真。";
+  Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR = "如果任一条件为真,则结果为真。";
+
+  // 布尔值配置
+  Blockly.Msg.LOGIC_BOOLEAN_TRUE = "真";
+  Blockly.Msg.LOGIC_BOOLEAN_FALSE = "假";
+  Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP = "返回一个布尔值:真或假。";
+
+  // 循环积木中文配置(保持不变)
+  Blockly.Msg.CONTROLS_REPEAT_TITLE = "重复%1次";
+  Blockly.Msg.CONTROLS_REPEAT_TOOLTIP = "重复执行内部代码指定次数。";
+
+  // 数学积木中文配置(保持不变)
+  Blockly.Msg.MATH_NUMBER_TOOLTIP = "一个数字。在编辑器中双击以更改。";
+  Blockly.Msg.MATH_ADDITION_SYMBOL = "加";
+  Blockly.Msg.MATH_SUBTRACTION_SYMBOL = "减";
+  Blockly.Msg.MATH_MULTIPLICATION_SYMBOL = "乘";
+  Blockly.Msg.MATH_DIVISION_SYMBOL = "除";
+  Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD = "返回两个数的和。";
+  Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_SUBTRACT = "返回第一个数减去第二个数的差。";
+  Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY = "返回两个数的积。";
+  Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE = "返回第一个数除以第二个数的商。";
+}
+
+/**
+ * 注册JavaScript生成器
+ * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
+ */
+export function registerJavaScriptGenerators(blocklySpecialBlocks = []) {
+  // 遍历所有可用生成器
+  for (const [blockName, generatorFunction] of Object.entries(availableGenerators)) {
+    // 检查对应的积木是否为通用积木或允许的特殊积木
+    const blockConfig = availableBlocks[blockName];
+    if (blockConfig && (blockConfig.isGeneral || blocklySpecialBlocks.includes(blockName))) {
+      javascriptGenerator.forBlock[blockName] = generatorFunction;
+    }
+  }
+
+  // 为重复循环块注册自定义生成器,确保支持异步操作(始终可用)
+  javascriptGenerator.forBlock['controls_repeat_ext'] = function(block) {
+    const repeats = javascriptGenerator.valueToCode(block, 'TIMES', javascriptGenerator.ORDER_ATOMIC) || '0';
+
+    // 确保获取到的是数字类型,如果是字符串需要转换
+    const safeRepeats = `(function() {
+      const num = Number(${repeats});
+      return isNaN(num) ? 0 : Math.max(0, Math.floor(num));
+    })()`;
+
+    // 获取循环体代码
+    const branch = javascriptGenerator.statementToCode(block, 'DO');
+
+    // 生成支持异步的循环代码
+    let code = `for (let i = 0; i < ${safeRepeats}; i++) {\n`;
+    code += javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT);
+    code += '}\n';
+
+    return code;
+  };
+
+  // 为text_print块添加生成器,用于调试(始终可用)
+  javascriptGenerator.forBlock['text_print'] = function(block) {
+    const msg = javascriptGenerator.valueToCode(block, 'TEXT', javascriptGenerator.ORDER_NONE) || '';
+    return msg;
+  };
+}
+
+/**
+ * 初始化Blockly工作区
+ * @param {Object} options - 初始化选项
+ * @returns {Blockly.Workspace} - 初始化后的Blockly工作区
+ */
+export function initBlockly(options = {}) {
+  // 应用中文配置
+  setupBlocklyChineseLocale();
+
+  const defaultOptions = {
+    toolbox: document.getElementById('toolbox'),
+    collapse: false,
+    comments: true,
+    disable: false, // 设为false以允许编辑
+    maxBlocks: Infinity,
+    trashcan: true,
+    horizontalLayout: false,
+    toolboxPosition: 'start',
+    toolboxCollapse: false, // 设置工具箱默认展开
+    toolboxAlwaysExpanded: true, // 确保点击类别后保持展开状态
+    css: true,
+    media: 'https://unpkg.com/blockly/media/',
+    rtl: false,
+    scrollbars: true,
+    sounds: false, // 禁用声音以提高性能
+    oneBasedIndex: true,
+    grid: {
+      spacing: 20,
+      length: 3,
+      colour: "#ccc",
+      snap: true
+    },
+    zoom: {
+      controls: true,
+      wheel: true,
+      startScale: 1.0,
+      maxScale: 3,
+      minScale: 0.3,
+      scaleSpeed: 1.2
+    }
+  };
+
+  // 合并默认选项和用户提供的选项
+  const finalOptions = { ...defaultOptions, ...options };
+
+  return Blockly.inject('blocklyDiv', finalOptions);
+}
+
+/**
+ * 获取所有可用的积木配置
+ * @returns {Object} 所有可用积木的配置对象
+ */
+export function getAllBlocksConfig() {
+  return {...availableBlocks};
+}
+
+/**
+ * 获取所有可用的生成器配置
+ * @returns {Object} 所有可用生成器的配置对象
+ */
+export function getAllGeneratorsConfig() {
+  return {...availableGenerators};
+}
+
+export default {
+  registerCustomBlocks,
+  registerJavaScriptGenerators,
+  setupBlocklyChineseLocale,
+  initBlockly,
+  getAllBlocksConfig,
+  getAllGeneratorsConfig
+};

+ 16 - 0
src/api/blockly/music.js

@@ -92,4 +92,20 @@ export const onMusicEnded = (state) => {
     state.currentMusicUrl = '';
     state.currentMusicName = '';
     console.log('音乐播放结束');
+};
+
+// 播放mp3文件
+export const playMp3 = async function(audioUrl) {
+    try {
+        // 创建音频对象,播放声音特效
+        const soundEffect = new Audio(audioUrl);
+
+        // 非阻塞方式播放音频
+        soundEffect.play().catch(error => {
+            console.error('播放声音特效失败:', error);
+        });
+
+    } catch (error) {
+        console.error('创建声音特效失败:', error);
+    }
 };

Datei-Diff unterdrückt, da er zu groß ist
+ 396 - 717
src/components/blockly/MapGame.vue


+ 8 - 5
src/views/programming/Interface.vue

@@ -28,7 +28,7 @@
           <VideoPlayer
               v-if="course.courseContentType === 'video'"
               :contentType="course.courseContentType"
-              :videoPath="course.courseVideoPath"
+              :videoPath="course.courseContent"
               :courseId="course.id || ''"
               :typeId="course.typeId"
               :courseConfigList="course.courseConfigList || []"
@@ -39,10 +39,10 @@
               @saveProgress="handleSaveProgress"
           />
           <!-- 图片 -->
-          <ImageView v-if="course.courseContentType === 'image'" :imagePath="course.courseImagePath" altText="课程图片"></ImageView>
+          <ImageView v-if="course.courseContentType === 'image'" :imagePath="course.courseContent" altText="课程图片"></ImageView>
 
           <!-- PPT -->
-          <PptView v-if="course.courseContentType === 'ppt'" :pptPath="course.pptPath" ref="pptRef"></PptView>
+          <PptView v-if="course.courseContentType === 'ppt'" :pptPath="course.courseContent" ref="pptRef"></PptView>
 
           <!--文生文-->
           <TextToText class="contentClass" v-if="course.courseContentType === 'aiTextToText'" ref="aiTextToText"></TextToText>
@@ -69,6 +69,7 @@
                    :info="course.blocklyInfo"
                    :game-title="course.courseName"
                    :course-list="props.courseList"
+                   :blockly-special-blocks="course.blocklySpecialBlocks"
                    :current-index="props.courseList.findIndex(item => item.id === course.id)"
                    @close-game="emit('closeVideo')"
                    @prev-section="playPreviousVideo"
@@ -342,7 +343,7 @@ const handleParentCourseData = (courseData = props.courseData) => {
     courseName: courseData.bcName,
     typeId: courseData.bcType,
     courseContentType: courseData.bcContentType,
-    courseVideoPath: courseData.bcContent,
+    courseContent: courseData.bcContent,
     courseConfigList: courseData.blocklyConfigList,
     key: courseData.id.toString(),
     // blockly相关属性,用于MapGame组件
@@ -353,7 +354,9 @@ const handleParentCourseData = (courseData = props.courseData) => {
     blocklyWalkablePoints: courseData.blocklyWalkablePoints,
     blocklyUserDirection: courseData.blocklyUserDirection || 0,
     blocklyUserImage: courseData.blocklyUserImage,
-    blocklyInfo: courseData.blocklyInfo
+    blocklyInfo: courseData.blocklyInfo,
+    blocklySpecialBlocks: courseData.blocklySpecialBlocks,
+
   };
   courseId.value = course.value.id;
   // 如果有配置,禁用视频检查

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.