Pārlūkot izejas kodu

Merge remote-tracking branch 'origin/wanzi'

# Conflicts:
#	src/components/blockly/MapGame.vue
liyanbo 4 mēneši atpakaļ
vecāks
revīzija
f55f53e2b3

+ 13 - 13
src/components/blockly/MapGame.vue

@@ -1818,22 +1818,22 @@ onUnmounted(() => {
     workspace.dispose();
     workspace = null;
   }
-  
+
   // 移除事件监听器
   window.removeEventListener('resize', updateMapContainerDimensions);
-  
+
   // 停止当前正在执行的代码
   shouldStopExecution = true;
-  
+
   // 取消任何正在进行的执行
   if (executionAbortController) {
     executionAbortController.abort();
     executionAbortController = null;
   }
-  
+
   // 清除当前执行的Promise引用
   currentExecutionPromise = null;
-  
+
   // 清理所有临时DOM元素
   const mapBackground = document.querySelector('.map-background');
   if (mapBackground) {
@@ -1844,7 +1844,7 @@ onUnmounted(() => {
         mapBackground.removeChild(video);
       }
     });
-    
+
     // 清除临时动画元素
     const tempElements = mapBackground.querySelectorAll('div[style*="backgroundImage"]');
     tempElements.forEach(temp => {
@@ -1853,11 +1853,11 @@ onUnmounted(() => {
       }
     });
   }
-  
+
   // 清理全局函数引用
   window.pickupItem = null;
   window.useItem = null;
-  
+
   // 重置游戏状态
   gameState.player.carriedItems = [];
   gameState.player.position = { ...startPoint.value };
@@ -2086,15 +2086,15 @@ onUnmounted(() => {
   flex: 1;
 }
 .message-item p {
-  margin: rpx(1) 0;
-  line-height: 1;
-  font-size: rpx(7);
+  margin: rpx(4) 0;
+  line-height: 0;
+  font-size: rpx(10);
   text-align: left;
   color: black;
   background-color: #e6faff;
   opacity: 0.8;
   border-radius: rpx(4);
-  padding: rpx(4);
+  padding: rpx(2) rpx(5);
   max-width: 100%;
 }
 .message-item p:first-child {
@@ -2125,7 +2125,7 @@ onUnmounted(() => {
 
 /* 右侧两个角为圆角的长方形格子样式 */
 .game-badge {
-  width: rpx(80);
+  width: rpx(70);
   height: rpx(20);
   margin-left: rpx(3);
   background-color: #5fb5dc;

+ 15 - 2
src/views/programming/Interface.vue

@@ -80,7 +80,7 @@
         </template>
 
         <!-- 视频切换按钮 - 始终显示 -->
-        <div class="video-switch">
+        <div class="video-switch" :class="{'map-game-mode': course.courseContentType === 'blockly'}">
           <div class="caret-left" @click="playPreviousVideo">
             <el-button type="warning" round :disabled="props.courseList.findIndex(item => item.id === course.id) === 0">
               <img :src="leftImg" alt="Left" />上一节</el-button 
@@ -447,6 +447,19 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   justify-content: center;
 }
 
+// MapGame地图模式下-上下节切换按钮-显示到右上角
+.video-switch.map-game-mode {
+  position: absolute;
+  top: rpx(5);
+  right: rpx(2);
+  width: auto;
+  justify-content: flex-end;
+  z-index: 1000;
+  margin: rpx(10);
+  gap: rpx(10);
+}
+
+
 .caret-right,
 .caret-left {
   width: rpx(50);
@@ -584,7 +597,7 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
 .small-title {
   width: 100%;
   // margin-top: rpx(-20);
-  height: rpx(20);
+  height: rpx(15);
   color: white;
   font-size: rpx(10);
   justify-content: center; //使子元素水平居中

+ 96 - 93
src/views/programming/ProgrammingCourset.vue

@@ -44,7 +44,7 @@
             :key="item.id"
             :class="['slide-item', { active: activeButton === index }]"
             :style="courseItems.length <= 3 ? { float: 'left' } : {}"
-            @click="handleCourseItemClick(item)"
+            @click="!isDragging && !hasMoved && handleCourseItemClick(item)"
           >
             <div class="box-content">
               <img :src="item.image" :alt="item.title" class="box-image" />
@@ -133,7 +133,7 @@ const resData = ref([])
 // 当前激活的按钮索引
 const activeButton = ref(0)
 
-// 拖动相关变量
+// 课程拖动变量
 const isDragging = ref(false)
 const startX = ref(0)
 const scrollLeft = ref(0)
@@ -151,14 +151,14 @@ const getStarCount = (contentType) => {
   }
 }
 
-// 上一
+// 上一节课程
 const prevSlide = () => {
   if (activeButton.value > 0) {
     activeButton.value -= 1;
   }
 }
 
-// 下一
+// 下一节课程
 const nextSlide = () => {
   if (activeButton.value < courseItems.value.length - 1) {
     activeButton.value += 1;
@@ -175,17 +175,16 @@ const handleMouseDown = (e) => {
   startX.value = e.pageX - contentBox.value.offsetLeft
   // 记录当前容器的滚动位置
   scrollLeft.value = contentBox.value.scrollLeft
-  e.stopPropagation() // 阻止事件冒泡 防止事件继续向上传播到父元素
 }
 
 // 鼠标移动事件处理函数
 const handleMouseMove = (e) => {
   if (!isDragging.value) return
+  
   // 标记已发生移动
   hasMoved.value = true
-  // 阻止默认行为和冒泡
-  e.preventDefault()
-  e.stopPropagation()
+  
+  // 计算新的滚动位置
   const x = e.pageX - contentBox.value.offsetLeft
   const walk = (x - startX.value) * 2 // 滚动速度
   contentBox.value.scrollLeft = scrollLeft.value - walk
@@ -193,18 +192,15 @@ const handleMouseMove = (e) => {
 
 // 鼠标松开事件处理函数
 const handleMouseUp = () => {
-  isDragging.value = false
-  // 延迟重置hasMoved,确保点击事件可以正常判断
-  setTimeout(() => {
-    hasMoved.value = false
-  }, 100)
-}
+  isDragging.value = false;
+  hasMoved.value = false;
+};
 
 // 鼠标离开事件处理函数
 const handleMouseLeave = () => {
-  isDragging.value = false
-  hasMoved.value = false
-}
+  isDragging.value = false;
+  hasMoved.value = false;
+};
 
 // rpx转换为像素值的辅助函数
 const rpxValue = (px) => {
@@ -217,85 +213,89 @@ const handleScroll = () => {}
 // 监听activeButton变化,自动滚动到中间位置
 watch(activeButton, (newIndex) => {
   if (contentBox.value && newIndex !== -1) {
-    // 找到对应的课程卡片元素
-    const courseElement = contentBox.value.querySelector(`.slide-item:nth-child(${newIndex + 1})`);
-    if (courseElement) {
-      // 计算滚动位置,选中的卡片居中
-      const containerWidth = contentBox.value.clientWidth;
-      const elementLeft = courseElement.offsetLeft;
-      const elementWidth = courseElement.offsetWidth;
-      // 计算居中位置
-      const scrollPosition = elementLeft - (containerWidth / 2) + (elementWidth / 2);
-      // 平滑滚动到计算的位置
-      contentBox.value.scrollTo({
-        left: scrollPosition,
-        behavior: 'smooth'
-      });
-    }
+    // 使用requestAnimationFrame优化滚动
+    requestAnimationFrame(() => {
+      // 找到对应的课程卡片元素
+      const courseElement = contentBox.value.querySelector(`.slide-item:nth-child(${newIndex + 1})`);
+      if (courseElement) {
+        // 计算滚动位置,选中的卡片居中
+        const containerWidth = contentBox.value.clientWidth;
+        const elementLeft = courseElement.offsetLeft;
+        const elementWidth = courseElement.offsetWidth;
+        // 计算居中位置
+        const scrollPosition = elementLeft - (containerWidth / 2) + (elementWidth / 2);
+        // 平滑滚动到计算的位置
+        contentBox.value.scrollTo({
+          left: scrollPosition,
+          behavior: 'smooth'
+        });
+      }
+    });
   }
 });
 
-// 提取课程数据获取和处理逻辑为单独函数
+// 获取课程数据函数优化
 const fetchCourseData = () => {
   if (typeId.value) {
     getBlocklyByTypeId(typeId.value).then(res => {
       if (res && res.data && Array.isArray(res.data)) {
-
-        // 保存原始API返回的数据
-        resData.value = res.data;
-        resData.value.forEach(item => {
+        // 使用局部变量处理数据,减少响应式对象的频繁修改
+        const newResData = [...res.data];
+        newResData.forEach(item => {
           item.isDisabled = isDisabled.value
-        })
+        });
+        
         // 创建图片映射,根据bcLabel显示对应图片
         const imageMap = {
           '1': explanation,
           '2': practice,
           '3': summary
-        }
-        // 清空旧的课程项
-        courseItems.value = [];
-        // 遍历接口返回的数据,设置课程项
-        res.data.forEach((item, index) => {
-          // 每个课程项都对应位置样式和图片
-          const positionIndex = (index % 3) + 1
+        };
+        
+        // 批量创建课程项
+        const newCourseItems = newResData.map((item, index) => {
+          const positionIndex = (index % 3) + 1;
           const image = imageMap[item.bcLabel]; // 根据bcLabel获取图片
-          courseItems.value.push({
+          return {
             id: item.id,
             title: item.bcName,
             image: image,
             contentType: item.bcContentType,
             progress: item.progress, // 进度
             isDisabled: isDisabled.value // 保存bcLabel用于图片映射
-          })
-        })
+          };
+        });
+        
+        // 一次性更新响应式数据,减少DOM更新次数
+        resData.value = newResData;
+        courseItems.value = newCourseItems;
       }
-    })
+    });
   }
 }
 
 // 组件挂载时获取路由参数设置标题
 onMounted(() => {
-  // 检查路由参数中是否有courseTitle
-  const courseTitle = route.query.courseTitle
+  // 批量获取并设置路由参数
+  const courseTitle = route.query.courseTitle;
   if (courseTitle) {
-    // 设置页面标题
-    pageTitle.value = courseTitle
+    pageTitle.value = courseTitle;
   }
-  // 获取当前类型ID
-  typeId.value = route.query.typeId
-  // 保存原始的课程ID和标题,返回时使用
-  originalCourseId.value = route.query.originalCourseId
-  originalCourseTitle.value = route.query.originalCourseTitle
-  isDisabled.value = Boolean(isDisabled.value)
+  
+  // 一次性设置多个响应式数据
+  typeId.value = route.query.typeId;
+  originalCourseId.value = route.query.originalCourseId;
+  originalCourseTitle.value = route.query.originalCourseTitle;
+  isDisabled.value = Boolean(isDisabled.value);
 
   // 获取到topicId后,调用函数获取课程列表
   fetchCourseData();
-})
+});
 
 // 处理课程项点击事件
 const handleCourseItemClick = (item) => {
-  // 如果在拖动过程中,则不触发点击事件
-  if (hasMoved.value) return
+  // 如果在拖动过程中或移动过,则不触发点击事件
+  if (hasMoved.value || isDragging.value) return
 
   // 如果是只读模式,不允许点击
   if (isDisabled.value) {
@@ -303,14 +303,13 @@ const handleCourseItemClick = (item) => {
     return;
   }
 
+  // 直接使用item中的信息,避免再次查找
   if (item.contentType === 'video' || item.contentType === 'blockly') {
     showVideo.value = true
-    // 查找并保存完整的课程数据
-    const fullCourseData = resData.value.find(course => course.id === item.id)
-    if (fullCourseData) {
-      selectedCourseData.value = fullCourseData
-      selectedCourseData.value.ztId = originalCourseId.value
-      selectedCourseData.value.isDisabled = isDisabled.value
+    selectedCourseData.value = {
+      ...resData.value.find(course => course.id === item.id),
+      ztId: originalCourseId.value,
+      isDisabled: isDisabled.value
     }
   }
 }
@@ -331,9 +330,11 @@ const goBackIndex = () => {
 
 // 监听showVideo状态变化,当从true变为false时重新获取课程数据
 watch(showVideo, (newValue, oldValue) => {
+  // 使用nextTick确保DOM更新完成后再执行
   if (oldValue === true && newValue === false) {
-    // 当视频界面关闭时,调用函数重新获取最新的课程数据
-    fetchCourseData();
+    setTimeout(() => {
+      fetchCourseData();
+    }, 0)
   }
 })
 </script>
@@ -475,7 +476,7 @@ watch(showVideo, (newValue, oldValue) => {
   padding: 0 rpx(30);
   /* 设置背景图  */
   background-image: url('@/assets/programming/track01.png');
-  background-size: rpx(1360) rpx(350); /* 使用固定宽度,背景图大小一致 */
+  background-size: rpx(1360) rpx(350); /* 固定宽度,背景图大小一致 */
   background-position: left calc(-1 * rpx(50));
   background-repeat: no-repeat;
   background-attachment: local; /* 背景图跟内容一起滚动 */
@@ -519,30 +520,32 @@ watch(showVideo, (newValue, oldValue) => {
 .slide-item:nth-child(even) {
   transform: translateY(50%);
 }
-/* 鼠标悬停放大效果 - 在保持原有垂直位置的基础上放大 */
-.slide-item:nth-child(odd):hover {
-  transform: translateY(-50%) scale(1.1);
-  z-index: 10;
-}
+/* 鼠标悬停放大效果 */
+  .slide-item:nth-child(odd):hover {
+    transform: translateY(-50%) scale(1.05); /* 减小放大比例 */
+    z-index: 10;
+    transition: transform 0.2s ease-out; /* 更短更平滑的过渡 */
+  }
 
-.slide-item:nth-child(even):hover {
-  transform: translateY(50%) scale(1.1);
-  z-index: 10;
-}
-/* 默认选中第一个元素的样式 */
-/* 奇数项选中样式 */
-.slide-item:nth-child(odd).active {
-  transform: translateY(-50%) scale(1.1);
-  z-index: 10;
-  box-shadow: 0 rpx(8) rpx(15) rgba(0, 0, 0, 0.3);
-}
+  .slide-item:nth-child(even):hover {
+    transform: translateY(50%) scale(1.05); /* 减小放大比例 */
+    z-index: 10;
+    transition: transform 0.2s ease-out; /* 更短更平滑的过渡 */
+  }
+  /* 默认选中第一个元素的样式 */
+  /* 奇数项选中样式 */
+  .slide-item:nth-child(odd).active {
+    transform: translateY(-50%) scale(1.05); /* 减小放大比例 */
+    z-index: 10;
+    box-shadow: 0 rpx(5) rpx(10) rgba(0, 0, 0, 0.2);
+  }
 
-/* 偶数项选中样式 */
-.slide-item:nth-child(even).active {
-  transform: translateY(50%) scale(1.1);
-  z-index: 10;
-  box-shadow: 0 rpx(8) rpx(15) rgba(0, 0, 0, 0.3);
-}
+  /* 偶数项选中样式 */
+  .slide-item:nth-child(even).active {
+    transform: translateY(50%) scale(1.05); /* 减小放大比例 */
+    z-index: 10;
+    box-shadow: 0 rpx(5) rpx(10) rgba(0, 0, 0, 0.2);
+  }
 
 /* 内容样式 */
 .box-content {

+ 1 - 0
src/views/programming/ProgrammingList.vue

@@ -396,6 +396,7 @@ const goToProgrammingList = (courseType, index) => {
   justify-content: center; /* 水平居中 */
   margin-right: rpx(20);
   margin-left: rpx(20);
+  margin-top: rpx(-15);
   vertical-align: middle; /* 垂直居中对齐 */
 }