Przeglądaj źródła

1、新增卡片横向滚动记住选中位置功能(AI实验课、blockly编程课分别三个页面)

liyanbo 3 miesięcy temu
rodzic
commit
ed0b84eb3d

+ 25 - 9
src/views/laboratory/ExperimentType.vue

@@ -92,7 +92,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, computed, watch, onMounted, onUnmounted } from 'vue'
+import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
 // 返回图标
 import { ArrowLeftBold } from '@element-plus/icons-vue';
 // 导入按钮图片
@@ -116,7 +116,6 @@ import { getExperimentTypeId } from '@/api/laboratory/index.js'
 const CONSTANTS = {
   SCROLL_SPEED: 2, // 滚动速度
   ANIMATION_DURATION: '0.3s',
-  DEFAULT_ACTIVE_INDEX: 0 // 索引
 }
 
 // 获取路由实例
@@ -132,14 +131,18 @@ const scrollLeft = ref(0)
 
 // 返回上一页
 const goBackToTopic = () => {
+  localStorage.removeItem(blocklyActiveButton.value)
+
   router.push('/aiCourseHome')
 }
 
 // 定义实验类型数据
 const experimentTypeList = reactive([])
 
+// 选中的按钮索引存储键名
+const blocklyActiveButton = ref("AiCourseActiveButton")
 // 当前激活的按钮索引
-const activeButton = ref(CONSTANTS.DEFAULT_ACTIVE_INDEX)
+const activeButton = ref(Number(localStorage.getItem(blocklyActiveButton.value)) || 0)
 
 // 计算属性:根据实验类型列表生成圆形按钮数据
 const circleButtons = computed(() => {
@@ -152,10 +155,16 @@ const circleButtons = computed(() => {
 const middleBox = ref(null)
 
 // 自动滚动到中间位置
-watch(activeButton, (newIndex) => {
-  if (middleBox.value && newIndex !== -1) {
+// 监听activeButton变化,切换数据项时的居中显示和放大效果
+watch(activeButton, () => {
+  middleBoxWidth()
+});
+
+// 中间卡片居中显示和放大效果
+const middleBoxWidth = ()=> {
+  if (middleBox.value) {
     // 找到对应的课程卡片元素
-    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 1})`);
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${activeButton.value + 1})`);
     if (courseElement) {
       // 计算滚动位置,选中的卡片居中
       const containerWidth = middleBox.value.clientWidth;
@@ -170,7 +179,7 @@ watch(activeButton, (newIndex) => {
       });
     }
   }
-});
+}
 
 // 鼠标按下事件处理函数
 const handleMouseDown = (e) => {
@@ -233,8 +242,11 @@ const fetchExperimentTypeList = async () => {
             isDisabled: item.dataReadonly
           });
         });
-        // 重置激活按钮索引,默认选中第一项
-        activeButton.value = CONSTANTS.DEFAULT_ACTIVE_INDEX;
+
+        // 数据加载完成后,等待下一个DOM更新周期,然后调用middleBoxWidth
+        nextTick(() => {
+          middleBoxWidth();
+        });
       }
     } catch (error) {
       console.error('Failed to fetch experiment type list:', error)
@@ -266,6 +278,10 @@ onUnmounted(() => {
 
 // 处理项目点击事件-跳转到课程详情页面
 const goToProgrammingList = (experimentType, index) => {
+
+  activeButton.value = index
+  localStorage.setItem(blocklyActiveButton.value, activeButton.value)
+
   // 检查是否禁用
   if (experimentType.isDisabled) {
     Message().notifyWarning('您的账号并未开放此课程!', true)

+ 50 - 34
src/views/laboratory/ExperimentalCourses.vue

@@ -31,7 +31,7 @@
     <div class="lower-box" v-if="!showVideo">
         <div 
             class="content-box"
-            ref="contentBox"
+            ref="middleBox"
             @mousedown="handleMouseDown"
             @mousemove="handleMouseMove"
             @mouseup="handleMouseUp"
@@ -44,7 +44,7 @@
             :key="item.id"
             :class="['slide-item', { active: activeButton === index }]"
             :style="courseList.length <= 3 ? { float: 'left' } : {}"
-            @click="!isDragging && !hasMoved && handleCourseItemClick(item)"
+            @click="!isDragging && !hasMoved && handleCourseItemClick(item, index)"
             v-memo="[activeButton === index, item.isDisabled]"
           >
             <div class="box-content">
@@ -91,7 +91,7 @@
 <script setup>
 // 返回图标
 import { ArrowLeftBold } from '@element-plus/icons-vue';
-import { ref, onMounted, onUnmounted, watch } from 'vue';
+import {ref, onMounted, onUnmounted, watch, nextTick} from 'vue';
 // 导入路由
 import { useRouter } from 'vue-router';
 // 导入按钮图片
@@ -111,7 +111,6 @@ import {DICT_TYPE} from "@/utils/dictUtils.js";
 const CONSTANTS = {
   SCROLL_SPEED: 2,
   ANIMATION_DURATION: '0.3s',
-  DEFAULT_ACTIVE_INDEX: 0,
   // 根据acLabel获取图片
   IMAGE_MAP: { 
     '1': "",
@@ -138,15 +137,17 @@ const courseList = ref([])
 const selectedCourseData = ref(null)
 // 保存原始API返回的数据
 const resData = ref([])
+// 选中的按钮索引存储键名
+const blocklyActiveButton = ref("aiCourseSectionActiveButton")
 // 当前激活的按钮索引
-const activeButton = ref(CONSTANTS.DEFAULT_ACTIVE_INDEX)
+const activeButton = ref(Number(localStorage.getItem(blocklyActiveButton.value)) || 0)
 // 拖动相关变量
 const isDragging = ref(false)
 const startX = ref(0)
 const scrollLeft = ref(0)
 const hasMoved = ref(false) // 标记是否发生了移动
 // 获取contentBox元素的引用
-const contentBox = ref(null)
+const middleBox = ref(null)
 
 // 根据内容类型获取星星数量
 const getStarCount = (contentType) => {
@@ -179,21 +180,21 @@ const handleMouseDown = (e) => {
   // 初始化移动状态为false
   hasMoved.value = false
   // 计算鼠标在容器内的相对X坐标的位置  e.pageX鼠标相对于整个页面的X坐标
-  if (contentBox.value) {
-    startX.value = e.pageX - contentBox.value.offsetLeft
-    scrollLeft.value = contentBox.value.scrollLeft
+  if (middleBox.value) {
+    startX.value = e.pageX - middleBox.value.offsetLeft
+    scrollLeft.value = middleBox.value.scrollLeft
   }
 }
 
 // 鼠标移动事件处理函数
 const handleMouseMove = (e) => {
-  if (!isDragging.value || !contentBox.value) return
+  if (!isDragging.value || !middleBox.value) return
   // 标记已发生移动
   hasMoved.value = true
   // 计算新的滚动位置
-  const x = e.pageX - contentBox.value.offsetLeft
+  const x = e.pageX - middleBox.value.offsetLeft
   const walk = (x - startX.value) * CONSTANTS.SCROLL_SPEED // 滚动速度
-  contentBox.value.scrollLeft = scrollLeft.value - walk
+  middleBox.value.scrollLeft = scrollLeft.value - walk
 }
 
 // 鼠标松开事件处理函数
@@ -212,29 +213,31 @@ const handleMouseLeave = () => {
 const handleScroll = () => {}
 
 // 监听activeButton变化,自动滚动到中间位置
-watch(activeButton, (newIndex) => {
-  if (contentBox.value && newIndex !== -1) {
-    // 使用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'
-        });
-      }
-    });
-  }
+watch(activeButton, () => {
+  middleBoxWidth()
 });
 
+// 中间卡片居中显示和放大效果
+const middleBoxWidth = ()=> {
+  if (middleBox.value) {
+    // 找到对应的课程卡片元素
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${activeButton.value + 1})`);
+    if (courseElement) {
+      // 计算滚动位置,选中的卡片居中
+      const containerWidth = middleBox.value.clientWidth;
+      const elementLeft = courseElement.offsetLeft;
+      const elementWidth = courseElement.offsetWidth;
+      // 计算居中位置
+      const scrollPosition = elementLeft - (containerWidth / 2) + (elementWidth / 2);
+      // 平滑滚动到计算的位置
+      middleBox.value.scrollTo({
+        left: scrollPosition,
+        behavior: 'smooth'
+      });
+    }
+  }
+}
+
 // 获取课程数据函数优化
 const fetchCourseData = async () => {
   if (typeId.value) {
@@ -263,6 +266,12 @@ const fetchCourseData = async () => {
         // 一次性更新响应式数据,减少DOM更新次数
         resData.value = newResData;
         courseList.value = newCourseItems;
+
+        // 数据加载完成后,等待下一个DOM更新周期,然后调用middleBoxWidth
+        nextTick(() => {
+          middleBoxWidth();
+        });
+
       }
     } catch (error) {
       console.error('Failed to fetch course data:', error)
@@ -306,7 +315,12 @@ onUnmounted(() => {
 });
 
 // 处理课程项点击事件
-const handleCourseItemClick = (item) => {
+const handleCourseItemClick = (item, index) => {
+
+  activeButton.value = index
+  localStorage.setItem(blocklyActiveButton.value, activeButton.value)
+
+
   // 如果在拖动过程中或移动过,则不触发点击事件
   if (hasMoved.value || isDragging.value) return
 
@@ -329,6 +343,8 @@ const handleCourseItemClick = (item) => {
 
 // 返回编程课列表
 const goBackIndex = () => {
+  localStorage.removeItem(blocklyActiveButton.value)
+
   // 隐藏视频和游戏界面
   showVideo.value = false
   // 返回时携带原始的课程参数

+ 23 - 5
src/views/laboratory/ExperimentalTheme.vue

@@ -98,7 +98,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, watch, onMounted, computed, onUnmounted } from 'vue'
+import {ref, reactive, watch, onMounted, computed, onUnmounted, nextTick} from 'vue'
 // 返回图标
 import { ArrowLeftBold } from '@element-plus/icons-vue';
 // 导入路由
@@ -123,8 +123,10 @@ const CONSTANTS = {
 
 // 创建路由实例
 const router = useRouter()
+// 选中的按钮索引存储键名
+const blocklyActiveButton = ref("AiCourseThemeActiveButton")
 // 当前激活的按钮索引
-const activeButton = ref(0)
+const activeButton = ref(Number(localStorage.getItem(blocklyActiveButton.value)) || 0)
 // 定义实验主题数据
 const experimentList = reactive([])
 // 用户名响应式变量
@@ -162,6 +164,11 @@ const fetchExperimentList = async () => {
           bgImage: item.ctTypeImage
         });
       });
+
+      // 数据加载完成后,等待下一个DOM更新周期,然后调用middleBoxWidth
+      nextTick(() => {
+        middleBoxWidth();
+      });
     }
   } catch (error) {
     console.error('获取实验主题列表失败:', error)
@@ -170,10 +177,15 @@ const fetchExperimentList = async () => {
 }
 
 // 监听activeButton变化,切换数据项时的居中显示和放大效果
-watch(activeButton, (newIndex) => {
+watch(activeButton, () => {
+  middleBoxWidth()
+});
+
+// 中间卡片居中显示和放大效果
+const middleBoxWidth = ()=> {
   if (middleBox.value) {
     // 找到对应的课程卡片元素
-    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 1})`);
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${activeButton.value + 1})`);
     if (courseElement) {
       // 计算滚动位置,选中的卡片居中
       const containerWidth = middleBox.value.clientWidth;
@@ -188,7 +200,7 @@ watch(activeButton, (newIndex) => {
       });
     }
   }
-});
+}
 
 // 鼠标按下事件处理函数
 const handleMouseDown = (e) => {
@@ -243,6 +255,10 @@ const nextExperiment = () => {
 
 // 跳转到实验类型页面
 const goToProgrammingList = (experiment, index) => {
+
+  activeButton.value = index
+  localStorage.setItem(blocklyActiveButton.value, activeButton.value)
+
   router.push({
     path: '/experiment-type',
     state: {
@@ -255,6 +271,8 @@ const goToProgrammingList = (experiment, index) => {
 
 // 返回到首页
 const goToHomePage = () => {
+  localStorage.removeItem(blocklyActiveButton.value)
+
   router.push({ path: '/home' })
 }
 

+ 27 - 9
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="!isDragging && !hasMoved && handleCourseItemClick(item)"
+            @click="!isDragging && !hasMoved && handleCourseItemClick(item, index)"
           >
             <div class="box-content">
               <img :src="item.image" :alt="item.title" class="box-image" />
@@ -90,7 +90,7 @@
 <script setup>
 // 返回图标
 import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
-import { ref, onMounted, computed, watch } from 'vue';
+import {ref, onMounted, computed, watch, nextTick} from 'vue';
 // 导入路由
 import { useRouter, useRoute } from 'vue-router';
 // 根据ID获取课程列表
@@ -127,8 +127,10 @@ const courseItems = ref([])
 const selectedCourseData = ref(null)
 // 保存原始API返回的数据
 const resData = ref([])
+// 选中的按钮索引存储键名
+const blocklyActiveButton = ref("blocklyCourseSectionIndexActiveButton")
 // 当前激活的按钮索引
-const activeButton = ref(0)
+const activeButton = ref(Number(localStorage.getItem(blocklyActiveButton.value)) || 0)
 
 // 课程拖动变量
 const isDragging = ref(false)
@@ -212,12 +214,20 @@ const rpxValue = (px) => {
 const handleScroll = () => {}
 
 // 监听activeButton变化,自动滚动到中间位置
-watch(activeButton, (newIndex) => {
-  if (contentBox.value && newIndex !== -1) {
+watch(activeButton, () => {
+  middleBoxWidth()
+});
+
+/**
+ * 居中显示选中的课程卡片
+ * 当课程卡片数量超过3个时,调用此函数居中显示选中的课程卡片
+ */
+const middleBoxWidth = ()=> {
+  if (contentBox.value && activeButton.value !== -1) {
     // 使用requestAnimationFrame优化滚动
     requestAnimationFrame(() => {
       // 找到对应的课程卡片元素
-      const courseElement = contentBox.value.querySelector(`.slide-item:nth-child(${newIndex + 1})`);
+      const courseElement = contentBox.value.querySelector(`.slide-item:nth-child(${activeButton.value + 1})`);
       if (courseElement) {
         // 计算滚动位置,选中的卡片居中
         const containerWidth = contentBox.value.clientWidth;
@@ -233,7 +243,7 @@ watch(activeButton, (newIndex) => {
       }
     });
   }
-});
+}
 
 // 获取课程数据函数优化
 const fetchCourseData = () => {
@@ -261,6 +271,11 @@ const fetchCourseData = () => {
         // 一次性更新响应式数据,减少DOM更新次数
         resData.value = newResData;
         courseItems.value = newCourseItems;
+
+        // 数据加载完成后,等待下一个DOM更新周期,然后调用middleBoxWidth
+        nextTick(() => {
+          middleBoxWidth();
+        });
       }
     });
   }
@@ -283,7 +298,6 @@ onMounted(() => {
   // 获取到topicId后,调用函数获取课程列表
   fetchCourseData();
 
-
   //课程小节字典
   let menuDictStr = localStorage.getItem(DICT_TYPE.BLOCKLY_COURSE_LABEL);
   let menuDictJson = menuDictStr ? JSON.parse(menuDictStr) : [];
@@ -294,7 +308,10 @@ onMounted(() => {
 });
 
 // 处理课程项点击事件
-const handleCourseItemClick = (item) => {
+const handleCourseItemClick = (item, index) => {
+  activeButton.value = index
+  localStorage.setItem(blocklyActiveButton.value, activeButton.value)
+
   // 如果在拖动过程中或移动过,则不触发点击事件
   if (hasMoved.value || isDragging.value) return
 
@@ -317,6 +334,7 @@ const handleCourseItemClick = (item) => {
 
 // 返回编程课列表
 const goBackIndex = () => {
+  localStorage.removeItem(blocklyActiveButton.value)
   // 隐藏视频和游戏界面
   showVideo.value = false
   // 返回时携带原始的课程参数

+ 31 - 9
src/views/programming/ProgrammingGame.vue

@@ -49,7 +49,10 @@
               class="middle-inner-box"
               @click.stop="activeButton = index"
             >
-              <div class="child-box-with-bg" :style="{ backgroundImage: `url(${course.bgImage})` }" :class="{ 'active': activeButton === index }" @click="goToProgrammingList(course, index)">
+              <div class="child-box-with-bg"
+                   :style="{ backgroundImage: `url(${course.bgImage})` }"
+                     :class="{ 'active': activeButton === index }"
+                   @click="goToProgrammingList(course, index)">
                 <div class="box-title">
                   <span>{{ course.title }}</span>
                 </div>
@@ -77,7 +80,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, watch, onMounted } from 'vue'
+import {ref, reactive, watch, onMounted, nextTick} from 'vue'
 // 返回图标
 import { ArrowLeftBold } from '@element-plus/icons-vue';
 // 导入路由
@@ -97,8 +100,10 @@ import {blocklyRoutes} from "@/router/index.js";
 
 // 创建路由实例
 const router = useRouter()
+// 选中的按钮索引存储键名
+const blocklyActiveButton = ref("blocklyThemeActiveButton")
 // 当前激活的按钮索引
-const activeButton = ref(0)
+const activeButton = ref(Number(localStorage.getItem(blocklyActiveButton.value)) || 0)
 // 定义圆形按钮数据
 const circleButtons = reactive([])
 // 定义课程类别数据
@@ -111,8 +116,8 @@ const updateUserName = () => {
 }
 
 // 获取主题列表
-getThemeList().then(res => {
-  console.log(res);
+const themeList = async () => {
+  let res = await getThemeList()
   if (res && res.data) {
     // 清空现有数据
     courseCategories.length = 0;
@@ -128,14 +133,24 @@ getThemeList().then(res => {
     courseCategories.forEach((_, index) => {
       circleButtons.push({ text: String(index + 1) });
     });
+
+    // 数据加载完成后,等待下一个DOM更新周期,然后调用middleBoxWidth
+    nextTick(() => {
+      middleBoxWidth();
+    });
   }
-})
+}
 
 // 监听activeButton变化,切换数据项时的居中显示和放大效果
-watch(activeButton, (newIndex) => {
+watch(activeButton, () => {
+  middleBoxWidth()
+});
+
+// 中间卡片居中显示和放大效果
+const middleBoxWidth = ()=> {
   if (middleBox.value) {
     // 找到对应的课程卡片元素
-    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 1})`);
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${activeButton.value + 1})`);
     if (courseElement) {
       // 计算滚动位置,选中的卡片居中
       const containerWidth = middleBox.value.clientWidth;
@@ -150,7 +165,7 @@ watch(activeButton, (newIndex) => {
       });
     }
   }
-});
+}
 
 // 拖动相关变量
 const isDragging = ref(false)
@@ -205,6 +220,9 @@ const nextSlide = () => {
 
 // 跳转到编程课程列表页
 const goToProgrammingList = (course, index) => {
+  activeButton.value = index
+  localStorage.setItem(blocklyActiveButton.value, activeButton.value)
+
   router.push({
     path: '/programmingList',
     state: {
@@ -217,6 +235,8 @@ const goToProgrammingList = (course, index) => {
 
 // 返回到首页
 const goToHomePage = () => {
+  localStorage.removeItem(blocklyActiveButton.value)
+
   router.push({
     path: '/home'
   })
@@ -228,6 +248,8 @@ const LogoutClick = async () => {
 }
 
 onMounted(() => {
+  // 获取主题列表
+  themeList()
   // 初始化用户名
   updateUserName()
   // storage事件监听器,监听其他标签页对localStorage的修改

+ 22 - 8
src/views/programming/ProgrammingList.vue

@@ -92,7 +92,7 @@
 </template>
 
 <script setup>
-import { ref, reactive, watch, onMounted } from 'vue'
+import {ref, reactive, watch, onMounted, nextTick} from 'vue'
 // 返回图标
 import { ArrowLeftBold } from '@element-plus/icons-vue';
 // 导入按钮图片
@@ -123,14 +123,17 @@ const categoryId = ref('')
 
 // 返回上一页
 const goBackIndex = () => {
+  localStorage.removeItem(blocklyActiveButton.value)
   router.push('/blocklyHome')
 }
 
 // 定义课程数据
 const specificCourses = reactive([])
 
+// 选中的按钮索引存储键名
+const blocklyActiveButton = ref("blocklyCourseActiveButton")
 // 当前激活的按钮索引
-const activeButton = ref(0)
+const activeButton = ref(Number(localStorage.getItem(blocklyActiveButton.value)) || 0)
 
 // 在获取到categoryId后再调用获取类型列表getTypeByThemeId接口
 const fetchTopicList = () => {
@@ -157,8 +160,11 @@ const fetchTopicList = () => {
         specificCourses.forEach((_, index) => {
           circleButtons.push({ text: String(index + 1) });
         });
-        // 重置激活按钮索引,默认选中第一项
-        activeButton.value = 0;
+
+        // 数据加载完成后,等待下一个DOM更新周期,然后调用middleBoxWidth
+        nextTick(() => {
+          middleBoxWidth();
+        });
       }
     })
   }
@@ -168,10 +174,14 @@ const fetchTopicList = () => {
 const circleButtons = reactive([])
 
 // 自动滚动到中间位置
-watch(activeButton, (newIndex) => {
-  if (middleBox.value && newIndex !== -1) {
+watch(activeButton, () => {
+  middleBoxWidth()
+});
+
+const middleBoxWidth = ()=> {
+  if (middleBox.value && activeButton.value !== -1) {
     // 找到对应的课程卡片元素
-    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 1})`);
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${activeButton.value + 1})`);
     if (courseElement) {
       // 计算滚动位置,选中的卡片居中
       const containerWidth = middleBox.value.clientWidth;
@@ -186,7 +196,8 @@ watch(activeButton, (newIndex) => {
       });
     }
   }
-});
+}
+
 // 拖动相关变量
 const isDragging = ref(false)
 const startX = ref(0)
@@ -255,6 +266,9 @@ onMounted(() => {
 
 // 处理项目点击事件-跳转到课程详情页面
 const goToProgrammingList = (courseType, index) => {
+  activeButton.value = index
+  localStorage.setItem(blocklyActiveButton.value, activeButton.value)
+
   // 检查是否禁用
   if (courseType.isDisabled) {
     Message().notifyWarning('您的账号并未开放此课程!', true)