Pārlūkot izejas kodu

Merge branch 'master' of http://59.110.91.129:3000/zhangmengying/AIClass into wanzi

丸子 5 mēneši atpakaļ
vecāks
revīzija
3c48d5f43c
2 mainītis faili ar 343 papildinājumiem un 79 dzēšanām
  1. 1 1
      .env
  2. 342 78
      src/views/block/MapGame.vue

+ 1 - 1
.env

@@ -5,7 +5,7 @@ VITE_APP_TITLE=AI课程网
 # VITE_BASE_URL='http://59.110.91.129:8088/admin-api'
 # VITE_BASE_URL='https://learn-ai.com.cn/admin-api'
 VITE_BASE_URL='http://192.168.110.8:8080/admin-api'
-
+    
 # 默认账户密码
 VITE_APP_DEFAULT_LOGIN_TENANT =
 VITE_APP_DEFAULT_LOGIN_USERNAME =

+ 342 - 78
src/views/block/MapGame.vue

@@ -51,7 +51,7 @@
                     :key="index"
                     class="carried-item"
                     :style="getCarriedItemStyle(index, item)"
-                ></div> 
+                ></div>
             </div>
           </div>
 
@@ -77,28 +77,18 @@
               <block type="use_item"></block>
             </category>
 
-            <!-- 逻辑控制积木 -->
             <category name="逻辑" colour="%{BKY_LOGIC_HUE}">
               <block type="controls_if"></block>
               <block type="logic_compare"></block>
               <block type="logic_operation"></block>
-              <block type="logic_negate"></block>
               <block type="logic_boolean"></block>
             </category>
 
-            <!-- 循环控制积木 -->
             <category name="循环" colour="%{BKY_LOOPS_HUE}">
-              <block type="controls_repeat_ext">
-                <value name="TIMES">
-                  <shadow type="math_number">
-                    <field name="NUM"></field>
-                  </shadow>
-                </value>
-              </block>
-              <block type="controls_whileUntil"></block>
+              <block type="controls_repeat_ext"></block>
+<!--              <block type="controls_whileUntil"></block>-->
             </category>
 
-            <!-- 数学运算积木 -->
             <category name="数学" colour="%{BKY_MATH_HUE}">
               <block type="math_number"></block>
               <block type="math_arithmetic"></block>
@@ -148,6 +138,8 @@ const CONFIG = {
     MESSAGE_DISPLAY: 2000,   // 消息显示时间
     COLLISION_RESET: 1000,   // 碰撞状态重置时间
     LOOP_PREVENTION: 10,     // 循环防止UI阻塞的延迟
+    ITEMS_APPEAR: 300,      // 物品出现延迟时间
+    ITEMS_FLIGHT_ANIMATION_DELAY: 800, // 飞行动画延迟时间
   },
   // 游戏配置
   GAME: {
@@ -684,6 +676,117 @@ function registerJavaScriptGenerators() {
   };
 }
 
+const 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.CONTROLS_WHILEUNTIL_TITLE_WHILE = "当%1时重复";
+  // Blockly.Msg.CONTROLS_WHILEUNTIL_TITLE_UNTIL = "重复直到%1";
+  // Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE = "只要条件为真,就重复执行内部代码。";
+  // Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL = "重复执行内部代码,直到条件变为真。";
+
+  // 数学积木中文配置(保持不变)
+  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 = "返回第一个数除以第二个数的商。";
+}
+
+
+// 初始化Blockly工作区
+function initBlockly() {
+  // 应用中文配置
+  setupBlocklyChineseLocale();
+
+  const toolbox = document.getElementById('toolbox');
+  workspace = Blockly.inject('blocklyDiv', {
+    toolbox: 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
+    }
+  });
+}
 
 
 // 平滑移动函数
@@ -814,7 +917,7 @@ async function slidingLogic() {
   }
 }
 
-// 处理陷阱逻辑
+// 处理任务逻辑
 async function taskLogic(tileMap) {
   if (shouldStopExecution || isColliding.value) {
     return;
@@ -824,36 +927,38 @@ async function taskLogic(tileMap) {
     // 判断当前位置是否有特殊需求
     if (tileMap && tileMap.type === BLOCKLY_MAP_TYPE_DICT.TASK && tileMap.must === true && tileMap.status !== true) {
       await handleWallCollision(tileMap.unfinishedTip);
+      return;
     }
   } catch (error) {
-    console.error('处理陷阱逻辑发生错误:', error);
+    console.error('处理任务逻辑发生错误:', error);
   }
 }
 
-// 物品拾取动画函数 - 放大晃动两下然后移动到左上角物品容器
+// 物品拾取动画函数
 async function animateItemPickup(item, itemX, itemY) {
   // 创建临时动画元素
   const tempItem = document.createElement('div');
   const iconSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO;
   const marginSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_MARGIN;
-  
+
   // 计算物品在地图上的实际位置
-  const mapContainer = document.querySelector('.map-container');
-  const mapRect = mapContainer.getBoundingClientRect();
   const itemLeft = itemX * tileSize.value - tileSize.value + marginSize;
   const itemTop = itemY * tileSize.value - tileSize.value + marginSize;
-  
+
+  // 平行移动到容器位置,同时调整大小
+  const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
+
   // 获取携带物品容器的位置(左上角)
-  const carriedContainer = document.querySelector('.carried-items-container');
-  let containerLeft = 30; // 默认值,如果容器不存在
-  let containerTop = 30; // 默认值,如果容器不存在
-  
-  if (carriedContainer) {
-    const containerRect = carriedContainer.getBoundingClientRect();
-    containerLeft = containerRect.left - mapRect.left + 10; // 加上内边距
-    containerTop = containerRect.top - mapRect.top + 10; // 加上内边距
+  // 注意:这里不需要考虑当前已携带物品数量,因为物品还没被添加
+  let containerLeft = 30;
+  let containerTop = 30;
+
+  // 如果已经有物品,计算新物品应该出现的位置
+  if (gameState.player.carriedItems.length > 0) {
+    let containerSize = finalSize + 10;
+    containerLeft = 30 + gameState.player.carriedItems.length * containerSize;
   }
-  
+
   // 设置临时元素样式
   Object.assign(tempItem.style, {
     position: 'absolute',
@@ -866,50 +971,162 @@ async function animateItemPickup(item, itemX, itemY) {
     backgroundPosition: 'center',
     backgroundRepeat: 'no-repeat',
     zIndex: '100', // 确保在最上层
-    transition: 'transform 0.3s ease-out',
+    opacity: '0', // 初始完全透明
     transform: 'scale(1)',
   });
-  
+
   // 将临时元素添加到地图容器
   const mapBackground = document.querySelector('.map-background');
+  if (!mapBackground) {
+    console.error('地图容器未找到');
+    return;
+  }
   mapBackground.appendChild(tempItem);
-  
-  // 强制重排,确保动画能正常触发
-  await nextTick();
-  
-  // 第一下放大晃动
-  tempItem.style.transform = 'scale(1.3) translateX(-5px)';
-  await new Promise(resolve => setTimeout(resolve, 150));
-  
-  // 第二下放大晃动
-  tempItem.style.transform = 'scale(1.3) translateX(5px)';
-  await new Promise(resolve => setTimeout(resolve, 150));
-  
-  // 准备移动动画
-  tempItem.style.transition = 'all 1.2s ease-out'; // 减慢动画速度
-  
-  // 平行移动到容器位置,同时调整大小
-  const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
+
+  // 触发淡入动画
+  tempItem.offsetHeight;
+  tempItem.style.transition = 'opacity ' + CONFIG.DELAY.ITEMS_APPEAR + 'ms ease-out';
+  tempItem.style.opacity = '1'; // 完全显示
+  await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
+
+  // 移动动画
+  tempItem.style.transition = 'all ' + CONFIG.DELAY.ITEMS_FLIGHT_ANIMATION_DELAY + 'ms ease-out'; // 减慢动画速度
   Object.assign(tempItem.style, {
     left: containerLeft + 'px',
     top: containerTop + 'px',
     width: finalSize + 'px',
     height: finalSize + 'px',
-    opacity: '0.8', // 稍微淡化
+    opacity: '0.8',
     transform: 'scale(1)',
   });
-  
+
   // 等待动画完成
-  await new Promise(resolve => setTimeout(resolve, 1200));
-  
+  await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_FLIGHT_ANIMATION_DELAY));
+
+  // 移除临时元素
+  if (mapBackground.contains(tempItem)) {
+    mapBackground.removeChild(tempItem);
+  }
+}
+
+// 物品使用动画函数
+async function animateItemUse(item, itemIndex) {
+  // 创建临时动画元素
+  const tempItem = document.createElement('div');
+  const finalSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
+  const iconSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO;
+  const marginSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_MARGIN;
+
+  // 计算物品在物品栏中的初始位置
+  let startLeft = 30;
+  let startTop = 30;
+  let containerSize = finalSize + 10;
+
+  // 根据物品索引计算在物品栏中的位置
+  if (itemIndex > 0) {
+    startLeft = 30 + itemIndex * containerSize;
+  }
+
+  // 计算玩家当前位置(物品目标位置)
+  const playerLeft = playerPosition.value.x * tileSize.value - tileSize.value + marginSize;
+  const playerTop = playerPosition.value.y * tileSize.value - tileSize.value + marginSize;
+
+  // 获取玩家元素
+  const playerElement = document.querySelector('.player');
+  const originalPlayerZIndex = playerElement ? playerElement.style.zIndex : '';
+
+  // 获取当前位置的任务点元素
+  let taskPointElement = null;
+  const walkablePointsElements = document.querySelectorAll('.walkable-point');
+  walkablePointsElements.forEach(el => {
+    const rect = el.getBoundingClientRect();
+    const targetRect = {
+      left: playerLeft,
+      top: playerTop,
+      width: iconSize,
+      height: iconSize
+    };
+    // 简单判断元素是否在玩家位置附近
+    if (Math.abs(rect.left - targetRect.left) < iconSize &&
+        Math.abs(rect.top - targetRect.top) < iconSize) {
+      taskPointElement = el;
+    }
+  });
+
+  // 先将任务图标置顶盖住人物
+  if (taskPointElement) {
+    taskPointElement.style.zIndex = '150';
+  }
+
+  // 设置临时元素初始样式
+  Object.assign(tempItem.style, {
+    position: 'absolute',
+    left: startLeft + 'px',
+    top: startTop + 'px',
+    width: finalSize + 'px',
+    height: finalSize + 'px',
+    backgroundImage: `url(${item.img})`,
+    backgroundSize: 'contain',
+    backgroundPosition: 'center',
+    backgroundRepeat: 'no-repeat',
+    zIndex: '120',
+    transition: 'transform ' + CONFIG.DELAY.ITEMS_APPEAR + 'ms ease-out',
+    transform: 'scale(1)',
+  });
+
+  // 将临时元素添加到地图容器
+  const mapBackground = document.querySelector('.map-background');
+  if (!mapBackground) {
+    console.error('地图容器未找到');
+    return;
+  }
+  mapBackground.appendChild(tempItem);
+
+  // 准备漂移动画
+  await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
+
+  // 设置漂移动画样式
+  tempItem.style.transition = 'all 1s ease-out';
+  Object.assign(tempItem.style, {
+    left: playerLeft + 'px',
+    top: playerTop + 'px',
+    width: iconSize + 'px',
+    height: iconSize + 'px',
+  });
+
+  // 等待漂移到玩家位置
+  await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_FLIGHT_ANIMATION_DELAY));
+
+  // 物品使用效果动画
+  tempItem.style.transition = 'transform 0.3s ease-in-out';
+  tempItem.style.transform = 'scale(1.2)';
+
+  await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
+
   // 移除临时元素
   if (mapBackground.contains(tempItem)) {
     mapBackground.removeChild(tempItem);
   }
+
+  // 延迟一段时间后将人物图标置顶,露出完整的人物图标
+  await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ITEMS_APPEAR));
+
+  // 恢复层级关系
+  if (taskPointElement) {
+    taskPointElement.style.zIndex = '';
+  }
+
+  if (playerElement) {
+    playerElement.style.zIndex = originalPlayerZIndex || '';
+  }
 }
 
 // 拾取物品函数
 window.pickupItem = async function()  {
+  if (shouldStopExecution || isColliding.value || isSliding.value) {
+    return;
+  }
+
   //取人物当前位置
   let x = playerPosition.value.x;
   let y = playerPosition.value.y;
@@ -921,27 +1138,29 @@ window.pickupItem = async function()  {
 
     // 处理携带物品逻辑
     if (tileMap && tileMap.img) {
-      // 执行物品拾取动画:放大晃动两下然后移动到左上角物品容器
-      await animateItemPickup(tileMap, x, y);
-      
-      // 将物品添加到玩家携带物品中
-      gameState.player.carriedItems.push({
-        ...tileMap,
-        originalX: x,
-        originalY: y
-      });
 
       // 从地图上移除图标(但保留点的可通行性)
       const pointIndex = gameState.mapData.walkablePoints.findIndex(
           p => p.x === x && p.y === y
       );
       if (pointIndex !== -1) {
+
         // 保留点但移除img属性
         const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
         delete updatedPoint.img;
         gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
         // 更新映射
         walkablePointsMap.set(`${x},${y}`, updatedPoint);
+
+        // 执行物品拾取动画:放大晃动两下然后移动到左上角物品容器
+        await animateItemPickup(tileMap, x, y);
+
+        // 将物品添加到玩家携带物品中
+        gameState.player.carriedItems.push({
+          ...tileMap,
+          originalX: x,
+          originalY: y
+        });
       }
     }
   } else {
@@ -953,6 +1172,10 @@ window.pickupItem = async function()  {
 
 // 使用物品函数
 window.useItem = async function() {
+  if (shouldStopExecution || isColliding.value || isSliding.value) {
+    return;
+  }
+
   //取人物当前位置
   let x = playerPosition.value.x;
   let y = playerPosition.value.y;
@@ -966,31 +1189,29 @@ window.useItem = async function() {
     let hasRequiredItem = false;
     let itemIndex = -1;
 
-    // 检查玩家携带的物品中是否有满足需求的物品【目前后端只配置了按顺序使用物品,没有指定物品】
-    // for (let i = 0; i < gameState.player.carriedItems.length; i++) {
-    //   const item = gameState.player.carriedItems[i];
-    //   if (requiredItems.includes(item.type)) {
-    //     hasRequiredItem = true;
-    //     itemIndex = i;
-    //     break;
-    //   }
-    // }
-
     if (gameState.player.carriedItems.length > 0) {
-          hasRequiredItem = true;
-          itemIndex = 0;
+      hasRequiredItem = true;
+      itemIndex = 0;
     }
 
     if (hasRequiredItem) {
 
+      // 获取要使用的物品
+      const itemToUse = gameState.player.carriedItems[itemIndex];
+      // 执行物品使用动画
+      await animateItemUse(itemToUse, itemIndex);
+      // 从携带物品中移除已使用的物品
+      gameState.player.carriedItems.splice(itemIndex, 1);
+
       // 从地图上移除图标(但保留点的可通行性)
       const pointIndex = gameState.mapData.walkablePoints.findIndex(
           p => p.x === x && p.y === y
       );
       if (pointIndex !== -1) {
-        // 保留点但移除img属性
+
+        // 保留点但移除img属性并设置完成状态图标
         const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
-        updatedPoint.img = updatedPoint.endImg;
+        updatedPoint.img = updatedPoint.endImg; // 设置完成状态图标
         updatedPoint.status = true;
         gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
 
@@ -1001,9 +1222,6 @@ window.useItem = async function() {
       // 使用物品成功
       showGameMessage(tileMap.finishedTip || CONFIG.TIPS.USE_ITEM_SUCCESS, 'success');
 
-      // 从携带物品中移除已使用的物品
-      gameState.player.carriedItems.splice(itemIndex, 1);
-
     } else {
       // 提示缺少所需物品
       showGameMessage(tileMap.unfinishedTip || CONFIG.TIPS.USE_SPECIAL_ITEM, 'warning');
@@ -1772,6 +1990,52 @@ onUnmounted(() => {
   border-radius: 8px;
 }
 
+/* 优化Blockly积木样式 */
+/* 增加积木高度 */
+.blocklyBlockCanvas .blocklyBlock {
+  height: 45px; /* 增加默认高度 */
+  min-height: 45px;
+}
+
+/* 增加积木内部元素的行高和间距 */
+.blocklyText {
+  font-size: 16px;
+  line-height: 20px;
+  font-weight: 500;
+}
+
+/* 增加输入字段的高度 */
+.blocklyHtmlInput {
+  height: 30px;
+  font-size: 14px;
+  padding: 5px;
+}
+
+/* 增加下拉菜单的高度 */
+.blocklyDropdownMenu {
+  line-height: 28px;
+  font-size: 14px;
+}
+
+/* 优化积木圆角和阴影效果 */
+.blocklyBlock {
+  border-radius: 8px;
+  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
+}
+
+/* 增加积木之间的连接点间距 */
+.blocklyConnection {
+  height: 20px;
+  width: 20px;
+}
+
+/* 增加工具箱中积木的高度 */
+.blocklyTreeRow {
+  height: 40px;
+  line-height: 40px;
+}
+
+
 .controls {
   display: flex;
   gap: 10px;