Procházet zdrojové kódy

1、blockly新页面

liyanbo před 6 měsíci
rodič
revize
996807b17a
3 změnil soubory, kde provedl 547 přidání a 15 odebrání
  1. 9 3
      src/api/blockly/music.js
  2. 2 0
      src/router/index.js
  3. 536 12
      src/views/Blockly.vue

+ 9 - 3
src/api/blockly/music.js

@@ -1,18 +1,23 @@
 // 导入ElMessage组件
 import { ElMessage } from 'element-plus';
 
+// 导入音乐文件 - 使用ES模块导入方式
+import huankuaiMp3 from '@/assets/music/huankuai.mp3';
+import shuhuanMp3 from '@/assets/music/shuhuan.mp3';
+
 // 音乐类型配置(独立配置,不直接关联颜色)
 export const musicTypeConfig = {
     '热闹': {
-        url: '/src/assets/music/huankuai.mp3',
+        url: huankuaiMp3,  // 使用导入的变量
         name: '热闹音乐'
     },
     '舒缓': {
-        url: '/src/assets/music/shuhuan.mp3',
+        url: shuhuanMp3,  // 使用导入的变量
         name: '舒缓音乐'
     }
 };
 
+// 播放音乐函数 - 修改为接受参数而不是直接使用外部变量
 // 播放音乐函数 - 修改为接受参数而不是直接使用外部变量
 export const playMusic = (musicType, state, musicPlayer) => {
     console.log(`播放音乐类型: ${musicType}`);
@@ -43,7 +48,7 @@ export const playMusic = (musicType, state, musicPlayer) => {
         }, 100);
     } else {
         // 如果没有找到对应类型的音乐,使用默认音乐
-        const defaultMusicType = '平静';
+        const defaultMusicType = '舒缓';
         const musicInfo = musicTypeConfig[defaultMusicType];
 
         state.currentMusicUrl = musicInfo.url;
@@ -67,6 +72,7 @@ export const playMusic = (musicType, state, musicPlayer) => {
     }
 };
 
+
 // 停止播放音乐 - 修改为接受参数
 export const stopMusic = (state, musicPlayer) => {
     if (musicPlayer.value) {

+ 2 - 0
src/router/index.js

@@ -10,6 +10,8 @@ const routes = [
   { path: '/quick-login', component: () => import('../views/QuickLogin.vue') },
   // Blockly
   { path: '/blockly', component: () => import('../views/Blockly.vue') },
+  { path: '/blockly2', component: () => import('../components/blockly/BlocklyEditor.vue') },
+  { path: '/blockly3', component: () => import('../components/blockly/BlocklyEditor_2.vue') },
   // 首页
   {
     path: '/home',

+ 536 - 12
src/views/Blockly.vue

@@ -403,6 +403,18 @@
           >
         </div>
 
+        <div class="json-section">
+          <h3>JSON 数据</h3>
+          <textarea v-model="jsonData" placeholder="在此输入JSON格式的积木块数据..."></textarea>
+          <div class="controls">
+            <button @click="loadWorkspaceFromJson">加载JSON到工作区</button>
+            <button @click="exportWorkspaceToJson">导出工作区为JSON</button>
+          </div>
+          <div v-if="statusMessage" :class="['status', statusType]" style="color: #1a1a1a">
+            {{ statusMessage }}
+          </div>
+        </div>
+
         <!-- 代码显示区 -->
         <div class="code-section">
           <h4>生成的代码</h4>
@@ -535,7 +547,7 @@ import { playMusic, stopMusic, onMusicEnded } from "@/api/blockly/music.js";
 
 const router = useRouter();
 // 台灯预览显示状态
-const showLampPreview = ref(true)
+const showLampPreview = ref(false)
 
 // 返回虚拟实验室
 const goBackLab = () => {
@@ -1289,12 +1301,8 @@ onMounted(async () => {
           .appendField('音乐类型:')
           .appendField(
               new Blockly.FieldDropdown([
-                ['平静', '平静'],
-                ['欢快', '欢快'],
+                ['热闹', '热闹'],
                 ['舒缓', '舒缓'],
-                ['激情', '激情'],
-                ['冥想', '冥想'],
-                ['自然', '自然']
               ]),
               'MUSIC_TYPE'
           );
@@ -1350,8 +1358,11 @@ onMounted(async () => {
       }
     });
 
+    // 使用合并的监听器函数
+    addCombinedChangeListener();
+
     // 使用当前Blockly版本支持的标准方法加载初始内容
-    const initialXml = "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n" +
+    /*const initialXml = "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n" +
         "      <variables>\n" +
         "        <variable id=\"kAVG*zJLw/q)l/(/eIMM\">inputText</variable>\n" +
         "        <variable id=\"[7F(niLz{.fvWvY.VT/N\">lampConfig</variable>\n" +
@@ -1412,7 +1423,7 @@ onMounted(async () => {
       console.warn('XML加载失败,尝试清空工作区:', xmlError);
       // 如果XML加载失败,清空工作区
       state.workspace.clear();
-    }
+    }*/
 
     // 添加拖拽修复
     if (state.workspace) {
@@ -1437,10 +1448,26 @@ onMounted(async () => {
     ElMessage.error('Blockly初始化失败,请刷新页面重试');
   }
 
+  // 修改工作区变化监听器,使其包含拖拽修复逻辑
+  state.workspace.addChangeListener((event) => {
+    // 生成代码
+    generateCode("javascript");
+
+    // 拖拽修复逻辑
+    if (event.type === Blockly.Events.BLOCK_CREATE) {
+      const block = state.workspace.getBlockById(event.blockId);
+      if (block) {
+        block.setEditable(true);
+      }
+    }
+  });
+
+  // 加载初始JSON数据
+  // workspace = state.workspace;
+  // loadWorkspaceFromJson();
+
   // 添加工作区变化监听器
-  if (state.workspace) {
-    state.workspace.addChangeListener(() => generateCode("javascript"));
-  }
+  //   state.workspace.addChangeListener(() => generateCode("javascript"));
 });
 
 // 组件卸载时清除所有资源
@@ -1460,6 +1487,450 @@ onUnmounted(() => {
   }
 });
 
+
+
+
+// 响应式变量
+const jsonData = ref(`{
+  "blocks": {
+    "languageVersion": 0,
+    "blocks": [
+      {
+        "type": "text_print",
+        "x": 100,
+        "y": 100,
+        "inputs": {
+          "TEXT": {
+            "block": {
+              "type": "text",
+              "fields": {
+                "TEXT": "Hello, Blockly!"
+              }
+            }
+          }
+        }
+      },
+      {
+        "type": "controls_if",
+        "x": 100,
+        "y": 200,
+        "inputs": {
+          "IF0": {
+            "block": {
+              "type": "logic_compare",
+              "fields": {
+                "OP": "EQ"
+              },
+              "inputs": {
+                "A": {
+                  "block": {
+                    "type": "math_number",
+                    "fields": {
+                      "NUM": 5
+                    }
+                  }
+                },
+                "B": {
+                  "block": {
+                    "type": "math_number",
+                    "fields": {
+                      "NUM": 5
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "DO0": {
+            "block": {
+              "type": "text_print",
+              "inputs": {
+                "TEXT": {
+                  "block": {
+                    "type": "text",
+                    "fields": {
+                      "TEXT": "条件成立!"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    ]
+  }
+}`);
+const statusMessage = ref('');
+const statusType = ref('');
+// let workspace = null;
+
+// 从JSON加载工作区 - 改进版
+const loadWorkspaceFromJson = () => {
+  try {
+    const json = JSON.parse(jsonData.value);
+
+    // 1. 确保工作区存在
+    if (!state.workspace) {
+      showStatus('工作区未初始化', 'error');
+      console.error('工作区未初始化');
+      return;
+    }
+
+    console.log('开始加载JSON数据:', JSON.stringify(json).substring(0, 200) + '...');
+
+    // 无论使用哪种策略加载成功后,确保所有块可拖拽
+    const ensureBlocksDraggable = () => {
+      const allBlocks = state.workspace.getAllBlocks();
+      allBlocks.forEach(block => {
+        // 强制设置为可编辑状态,不做任何条件检查
+        block.setEditable(true);
+        // 添加调试日志,确认每个块都被设置为可编辑
+        console.log(`设置块${block.id}为可编辑状态`);
+      });
+      return allBlocks.length;
+    };
+
+    // 2. 尝试多种加载策略
+    try {
+      // 策略1: 先清空工作区再加载
+      state.workspace.clear();
+      Blockly.serialization.workspaces.load(json, state.workspace);
+      const blockCount = ensureBlocksDraggable(); // 调用ensureBlocksDraggable确保所有块可拖拽
+      showStatus(`工作区已成功从JSON加载!共加载${blockCount}个积木。`, 'success');
+      console.log(`策略1成功: 加载了${blockCount}个积木`);
+      return;
+    } catch (error1) {
+      console.warn('策略1失败,尝试策略2:', error1);
+
+      try {
+        // 策略2: 清理JSON后再加载
+        state.workspace.clear();
+        const cleanedJson = deepCleanJsonForConnectionIssues(json);
+        Blockly.serialization.workspaces.load(cleanedJson, state.workspace);
+        const blockCount = ensureBlocksDraggable(); // 调用ensureBlocksDraggable确保所有块可拖拽
+        showStatus(`工作区已通过清理后的JSON成功加载!共加载${blockCount}个积木。`, 'success');
+        console.log(`策略2成功: 加载了${blockCount}个积木`);
+        return;
+      } catch (error2) {
+        console.warn('策略2失败,尝试策略3:', error2);
+
+        try {
+          // 策略3: 直接使用XML序列化API作为中间格式
+          state.workspace.clear();
+
+          // 先将JSON转换为XML字符串
+          const xmlText = jsonToXml(json);
+          console.log('生成的XML:', xmlText.substring(0, 200) + '...');
+
+          // 加载XML到工作区
+          const xmlDom = Blockly.utils.xml.textToDom(xmlText);
+          Blockly.Xml.domToWorkspace(xmlDom, state.workspace);
+
+          const blockCount = ensureBlocksDraggable(); // 调用ensureBlocksDraggable确保所有块可拖拽
+          showStatus(`工作区已通过XML中间格式成功加载!共加载${blockCount}个积木。`, 'success');
+          console.log(`策略3成功: 加载了${blockCount}个积木`);
+          return;
+        } catch (error3) {
+          console.warn('策略3失败,尝试策略4:', error3);
+
+          try {
+            // 策略4: 创建新的工作区实例并使用XML导入
+            // 先释放旧工作区资源
+            if (state.workspace) {
+              state.workspace.dispose();
+            }
+
+            // 创建新的工作区
+            const blocklyDiv = document.getElementById('blocklyDiv');
+            const toolbox = document.getElementById('toolbox');
+
+            state.workspace = Blockly.inject(blocklyDiv, {
+              toolbox: toolbox,
+              collapse: true,
+              comments: true,
+              disable: false,
+              maxBlocks: Infinity,
+              trashcan: true,
+              horizontalLayout: false,
+              toolboxPosition: 'start',
+              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
+              }
+            });
+
+            // 重新添加工作区变化监听器 - 使用合并后的监听器
+            addCombinedChangeListener();
+
+            // 使用XML作为中间格式导入
+            const xmlText = jsonToXml(json);
+            console.log('创建新工作区,生成的XML:', xmlText.substring(0, 200) + '...');
+            const xmlDom = Blockly.utils.xml.textToDom(xmlText);
+            Blockly.Xml.domToWorkspace(xmlDom, state.workspace);
+
+            const blockCount = ensureBlocksDraggable(); // 调用ensureBlocksDraggable确保所有块可拖拽
+            showStatus(`工作区已通过创建新实例并使用XML格式成功加载!共加载${blockCount}个积木。`, 'success');
+            console.log(`策略4成功: 加载了${blockCount}个积木`);
+            return;
+          } catch (error4) {
+            // 所有策略都失败
+            showStatus('JSON解析错误: ' + error4.message, 'error');
+            console.error('所有加载策略均失败:', error4);
+          }
+        }
+      }
+    }
+  } catch (error) {
+    showStatus('JSON解析错误: ' + error.message, 'error');
+    console.error('JSON解析错误:', error);
+  }
+};
+
+// 2. 创建一个合并的工作区变化监听器函数
+function addCombinedChangeListener() {
+  if (!state.workspace) return;
+
+  // 移除旧的监听器(如果有)
+  state.workspace.removeAllChangeListeners();
+
+  // 添加新的合并监听器
+  state.workspace.addChangeListener((event) => {
+    // 生成代码
+    generateCode("javascript");
+
+    // 拖拽修复逻辑
+    if (event.type === Blockly.Events.BLOCK_CREATE || event.type === Blockly.Events.BLOCK_ADD)
+    {
+      const block = state.workspace.getBlockById(event.blockId);
+      if (block) {
+        // 强制设置为可编辑状态
+        block.setEditable(true);
+        console.log(`监听器设置块${block.id}为可编辑状态`);
+      }
+    }
+  });
+}
+
+// 导出工作区为JSON - 增强版
+const exportWorkspaceToJson = () => {
+  try {
+    if (!state.workspace) {
+      showStatus('工作区未初始化', 'error');
+      return;
+    }
+
+    // 使用state.workspace替代workspace
+    const stateJson = Blockly.serialization.workspaces.save(state.workspace);
+    jsonData.value = JSON.stringify(stateJson, null, 2);
+
+    // 显示导出的积木数量
+    const blockCount = state.workspace.getAllBlocks().length;
+    showStatus(`工作区已成功导出为JSON!共导出${blockCount}个积木。`, 'success');
+    console.log(`导出JSON成功: 导出了${blockCount}个积木`);
+  } catch (error) {
+    showStatus('导出错误: ' + error.message, 'error');
+    console.error('导出错误:', error);
+  }
+};
+
+// 深度清理JSON中可能导致连接问题的部分
+function deepCleanJsonForConnectionIssues(json) {
+  const cleanedJson = JSON.parse(JSON.stringify(json));
+
+  // 1. 清理blocks结构
+  if (cleanedJson.blocks && cleanedJson.blocks.blocks) {
+    cleanedJson.blocks.blocks.forEach(block => {
+      // 2. 移除inputs中的connection引用
+      if (block.inputs) {
+        Object.keys(block.inputs).forEach(inputKey => {
+          const input = block.inputs[inputKey];
+          if (input.connection) {
+            delete input.connection;
+          }
+
+          // 3. 递归清理嵌套的block
+          if (input.block) {
+            cleanBlockConnections(input.block);
+          }
+        });
+      }
+
+      // 4. 清理next和previous连接
+      if (block.next) {
+        if (block.next.connection) {
+          delete block.next.connection;
+        }
+        if (block.next.block) {
+          cleanBlockConnections(block.next.block);
+        }
+      }
+
+      if (block.previous) {
+        if (block.previous.connection) {
+          delete block.previous.connection;
+        }
+      }
+    });
+  }
+
+  return cleanedJson;
+}
+
+// 递归清理单个block的连接信息
+function cleanBlockConnections(block) {
+  if (!block) return;
+
+  // 清理inputs中的连接
+  if (block.inputs) {
+    Object.keys(block.inputs).forEach(inputKey => {
+      const input = block.inputs[inputKey];
+      if (input.connection) {
+        delete input.connection;
+      }
+      if (input.block) {
+        cleanBlockConnections(input.block);
+      }
+    });
+  }
+
+  // 清理next和previous连接
+  if (block.next) {
+    if (block.next.connection) {
+      delete block.next.connection;
+    }
+    if (block.next.block) {
+      cleanBlockConnections(block.next.block);
+    }
+  }
+
+  if (block.previous) {
+    if (block.previous.connection) {
+      delete block.previous.connection;
+    }
+  }
+}
+
+// 将JSON转换为XML(增强版)
+function jsonToXml(json) {
+  // 先检查JSON是否有效
+  if (!json || typeof json !== 'object') {
+    console.error('无效的JSON数据');
+    return '<xml xmlns="https://developers.google.com/blockly/xml"></xml>';
+  }
+
+  let xml = '<xml xmlns="https://developers.google.com/blockly/xml">';
+
+  // 1. 处理变量
+  if (json.variables && json.variables.variables) {
+    xml += '<variables>';
+    json.variables.variables.forEach(variable => {
+      xml += `<variable id="${escapeXml(variable.id)}">${escapeXml(variable.name)}</variable>`;
+    });
+    xml += '</variables>';
+  }
+
+  // 2. 处理积木块 - 这是关键部分
+  if (json.blocks && json.blocks.blocks) {
+    // 找出所有根节点积木(没有previous连接的积木)
+    const rootBlocks = json.blocks.blocks.filter(block => !block.previous);
+    console.log(`找到${rootBlocks.length}个根积木块`);
+
+    // 递归生成每个根积木块的XML
+    rootBlocks.forEach(block => {
+      xml += generateBlockXml(block);
+    });
+  } else {
+    console.warn('JSON中没有找到blocks数据');
+  }
+
+  xml += '</xml>';
+  return xml;
+}
+
+// 递归生成单个积木块的XML
+function generateBlockXml(block) {
+  if (!block) return '';
+
+  let blockXml = `<block type="${escapeXml(block.type)}" id="${escapeXml(block.id)}" x="${block.x || 0}" y="${block.y || 0}">`;
+
+  // 处理fields
+  if (block.fields) {
+    Object.keys(block.fields).forEach(fieldName => {
+      const fieldValue = block.fields[fieldName];
+      blockXml += `<field name="${escapeXml(fieldName)}">${escapeXml(String(fieldValue))}</field>`;
+    });
+  }
+
+  // 处理inputs
+  if (block.inputs) {
+    Object.keys(block.inputs).forEach(inputName => {
+      const input = block.inputs[inputName];
+      if (input.block) {
+        blockXml += `<value name="${escapeXml(inputName)}">`;
+        blockXml += generateBlockXml(input.block);
+        blockXml += '</value>';
+      }
+    });
+  }
+
+  // 处理嵌套的next积木
+  if (block.next && block.next.block) {
+    blockXml += '<next>';
+    blockXml += generateBlockXml(block.next.block);
+    blockXml += '</next>';
+  }
+
+  blockXml += '</block>';
+  return blockXml;
+}
+
+// XML转义函数
+function escapeXml(text) {
+  if (typeof text !== 'string') {
+    text = String(text);
+  }
+  return text
+      .replace(/&/g, '&amp;')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/"/g, '&quot;')
+      .replace(/'/g, '&apos;');
+}
+
+
+
+// 显示状态消息
+const showStatus = (message, type) => {
+  statusMessage.value = message;
+  statusType.value = type;
+
+  // 3秒后自动清除状态消息
+  setTimeout(() => {
+    statusMessage.value = '';
+  }, 3000);
+};
+
+
+
+
+
 // 注册JavaScript代码生成器
 function registerJavaScriptGenerators() {
   // 语音输入
@@ -2018,6 +2489,7 @@ window.aiService = aiService;
     }
   }
 
+  //运行结果区域样式
   .result-section {
     flex: 1;
     display: flex;
@@ -2025,7 +2497,8 @@ window.aiService = aiService;
 
     .run-result-content {
       color: black;
-      flex: 1;
+      // 固定高度为300px,可根据需要调整
+      height: 180px;
       background-color: #f8f9fa;
       border: 1px solid #ddd;
       border-radius: 4px;
@@ -2033,6 +2506,30 @@ window.aiService = aiService;
       overflow-y: auto;
       font-family: monospace;
       font-size: 14px;
+
+      // 优化滚动条样式 - 适用于Webkit浏览器(Chrome, Safari)
+      &::-webkit-scrollbar {
+        width: 8px;
+        height: 8px;
+      }
+
+      &::-webkit-scrollbar-track {
+        background: #f1f1f1;
+        border-radius: 4px;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        background: #c1c1c1;
+        border-radius: 4px;
+      }
+
+      &::-webkit-scrollbar-thumb:hover {
+        background: #a8a8a8;
+      }
+
+      // Firefox滚动条样式
+      scrollbar-width: thin;
+      scrollbar-color: #c1c1c1 #f1f1f1;
     }
   }
 }
@@ -2176,4 +2673,31 @@ window.aiService = aiService;
   color: #666;
   padding: 5px 0;
 }
+
+
+
+
+
+.json-section {
+  margin-top: 15px;
+  padding: 15px;
+  background: #f1f8ff;
+  border-radius: 8px;
+  border: 1px solid #d1e7ff;
+}
+
+.json-section h3 {
+  margin-bottom: 10px;
+  color: #2c3e50;
+}
+
+textarea {
+  width: 100%;
+  min-height: 120px;
+  padding: 10px;
+  border: 1px solid #ddd;
+  border-radius: 5px;
+  font-family: 'Courier New', monospace;
+  resize: vertical;
+}
 </style>