Эх сурвалжийг харах

Merge branch 'wanzi'

# Conflicts:
#	src/views/block/MapGame.vue
liyanbo 5 сар өмнө
parent
commit
4475018738

BIN
src/assets/programming/bg01.png


BIN
src/assets/programming/bg02.png


BIN
src/assets/programming/bg03.png


BIN
src/assets/programming/bg04.png


BIN
src/assets/programming/bg05.png


BIN
src/assets/programming/bg06.png


BIN
src/assets/programming/explanation.png


BIN
src/assets/programming/list_bg.png


BIN
src/assets/programming/list_bg02.png


BIN
src/assets/programming/list_bg03.png


BIN
src/assets/programming/list_img01.png


BIN
src/assets/programming/list_img02.png


BIN
src/assets/programming/list_img03.png


BIN
src/assets/programming/list_title.png


BIN
src/assets/programming/lock.png


BIN
src/assets/programming/practice.png


BIN
src/assets/programming/summary.png


BIN
src/assets/programming/track.png


BIN
src/assets/programming/unlock.png


BIN
src/assets/programming/网页ui-06.png


BIN
src/assets/programming/网页ui-21.png


BIN
src/assets/programming/网页ui-22.png


+ 15 - 0
src/router/index.js

@@ -90,6 +90,21 @@ const routes = [
   { path: '/mapGame', component: () => import('../views/block/MapGame.vue') },
   // 编程游戏列表
   { path: '/programming', component: () => import('../views/gamepage/GameIndex.vue') },
+  // 编程课
+  {
+    path: '/programming02', 
+    component: () => import('../views/programming/ProgrammingGame.vue')
+  },
+  // 编程课列表
+    {
+    path: '/programminglist', 
+    component: () => import('../views/programming/ProgrammingList.vue')
+  },
+  // 编程课列表内容
+  {
+    path: '/programmingcourset', 
+    component: () => import('../views/programming/ProgrammingCourset.vue')
+  },
 ]
 const router = createRouter({
   history: createWebHistory(),

+ 4 - 0
src/views/block/MapGame.vue

@@ -77,6 +77,7 @@
               <block type="use_item"></block>
             </category>
 
+            <!-- 逻辑控制积木 -->
             <category name="逻辑" colour="%{BKY_LOGIC_HUE}">
               <block type="controls_if"></block>
               <block type="logic_compare"></block>
@@ -84,11 +85,13 @@
               <block type="logic_boolean"></block>
             </category>
 
+            <!-- 循环控制积木 -->
             <category name="循环" colour="%{BKY_LOOPS_HUE}">
               <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>
@@ -294,6 +297,7 @@ onMounted(async () => {
   await fetchGameData();
   // 初始化可行走点集合
   initWalkablePointsSet();
+
   // 注册自定义积木
   registerCustomBlocks();
   // 注册JavaScript生成器

+ 280 - 0
src/views/programming/ProgrammingCourset.vue

@@ -0,0 +1,280 @@
+<!-- 编程课列表内容 -->
+<template>
+  <div class="programming-content">
+    <!-- 标题部分 -->
+    <div class="upper-box">
+      <!-- 返回按钮 -->
+      <div class="left-box">
+        <div class="top-left-inner-box">
+          <!-- 左侧返回图标 -->
+          <el-icon class="left-icon" @click="goBackIndex"><ArrowLeftBold /></el-icon>
+          <span class="left-text">返回</span>
+        </div>
+      </div>
+      <!-- 标题 -->
+      <div class="middle-box">
+        <div class="top-center-inner-box" style="background-image: url('./src/assets/programming/list_title.png');">
+          <span>{{ pageTitle }}</span>
+        </div>
+      </div>
+      <!-- 课程提示 -->
+      <div class="right-box">
+        <div class="top-right-inner-box">
+          <div class="course-info-box">初始编程</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 课程部分 -->
+    <div class="lower-box">
+      <div class="content-box">
+        <!-- 动态渲染课程内容 -->
+        <div 
+          v-for="item in courseItems" 
+          :key="item.id"
+          :class="item.positionClass"
+        >
+          <div class="box-content">
+            <img :src="item.image" :alt="item.title" class="box-image" />
+            <div class="box-text">{{ item.title }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 返回图标
+import { ArrowLeftBold } from '@element-plus/icons-vue';
+import { ref, onMounted } from 'vue';
+// 导入路由
+import { useRouter, useRoute } from 'vue-router';
+
+// 导入图片
+import  explanation  from '@/assets/programming/explanation.png'
+import  practice  from '@/assets/programming/practice.png'
+import  summary  from '@/assets/programming/summary.png'
+
+// 获取路由实例
+const router = useRouter()
+const route = useRoute()
+// 页面标题
+const pageTitle = ref('')
+// 返回编程课列表
+const goBackIndex = () => {
+  router.push('/programminglist')
+}
+
+// 动态课程项数据
+const courseItems = ref([
+  {
+    id: 1,
+    title: '知识讲解',
+    image: explanation,
+    positionClass: 'left-content-box'
+  },
+  {
+    id: 2,
+    title: '实操练习',
+    image: practice,
+    positionClass: 'center-content-box'
+  },
+  {
+    id: 3,
+    title: '课程小结',
+    image: summary,
+    positionClass: 'right-content-box'
+  }
+])
+
+// 组件挂载时获取路由参数设置标题
+onMounted(() => {
+  // 检查路由参数中是否有courseTitle
+  const courseTitle = route.query.courseTitle
+  if (courseTitle) {
+    // 设置页面标题
+    pageTitle.value = courseTitle
+  }
+})
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+.programming-content{
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-image: url('@/assets/programming/list_bg03.png');
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  display: flex;
+  flex-direction: column;
+}
+
+.upper-box {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+}
+
+.left-box, .middle-box, .right-box {
+  flex: 1;
+  margin: rpx(5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.top-center-inner-box{
+  width: 100%;
+  height: 100%;
+  background-size: 100%; 
+  background-position: 50% 30%;
+  background-repeat: no-repeat;
+  display: flex; 
+  align-items: center; 
+  justify-content: center; 
+  span{
+      font-size: rpx(15);
+      color: white;
+  }
+}
+
+.top-left-inner-box{
+  display: flex;
+  align-items: center; /* 垂直居中对齐 */
+  width: 100%;
+  height: 100%;
+  .left-icon{
+    font-size: rpx(14);
+    color: white;
+    padding-left: rpx(20);
+    cursor: pointer;
+  }
+  .left-text{
+    font-size: rpx(14);
+    color: white;
+    padding-left: rpx(10);
+    cursor: pointer;
+  }
+}
+
+.top-right-inner-box {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  padding-right: rpx(20);
+  .course-info-box {
+    width: rpx(60);
+    height: rpx(20);
+    background-color: rgb(255, 255, 255);
+    border-radius: rpx(15);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #45300b;
+    font-size: rpx(10);
+  }
+}
+
+.lower-box {
+  flex: 5;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+}
+
+.content-box {
+  width: 100%;
+  height: 100%;
+  background-image: url('@/assets/programming/track.png');
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  display: flex;
+  justify-content: space-between;
+  position: relative;
+}
+
+.left-content-box, .center-content-box, .right-content-box {
+  flex: 1;
+  height: 40%; /* 高度缩小 */
+  margin: 0 rpx(10);
+  border-radius: rpx(40);
+  background-color: rgba(255, 255, 255);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 rpx(5) rpx(10) rgba(0, 0, 0, 0.2);
+  transition: transform 0.3s ease;
+  cursor: pointer;
+}
+
+/* 位置调整 */
+.left-content-box {
+  position: absolute;
+  left: 5%;
+  top: 10%; /* 偏上 */
+  width: calc(20% - rpx(10));
+}
+
+.center-content-box {
+  position: absolute;
+  left: 48%;
+  transform: translateX(-50%);
+  bottom: 15%; /* 偏下 */
+  width: calc(20% - rpx(10));
+}
+
+.right-content-box {
+  position: absolute;
+  right: 5%;
+  top: 10%; /* 偏上 */
+  width: calc(20% - rpx(10));
+}
+
+/* 内容样式 */
+.box-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  width: 100%;
+}
+
+/* 鼠标悬停放大效果 */
+.left-content-box:hover,
+.right-content-box:hover{
+  transform: scale(1.1);
+}
+.center-content-box:hover {
+  transform: translateX(-50%) scale(1.1);
+}
+.box-image {
+  width: 100%;
+  height: 90%;
+  object-fit: contain;
+}
+
+.box-text {
+  width: 100%;
+  height: 20%;
+  line-height: 20%;
+  font-size: rpx(13);
+  color: #333;
+  text-align: center;
+}
+</style>

+ 370 - 0
src/views/programming/ProgrammingGame.vue

@@ -0,0 +1,370 @@
+<!-- 编程课02 -->
+<template>
+    <div class="programming-content">
+      <!-- 标题部分 -->
+      <div class="top-box">
+        <div class="top-left-box">
+          <div class="top-left-inner-box">
+            <!-- 左侧返回图标 -->
+             <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
+             <span class="left-text">首页</span>
+          </div>
+        </div>
+        <div class="top-center-box">
+          <div class="top-center-inner-box" style="background-image: url('./src/assets/programming/list_title.png');">
+            <span>AI编程课</span>
+          </div>
+        </div>
+        <div class="top-right-box">
+          <div class="top-right-inner-box"></div>
+        </div>
+      </div>
+
+        <!-- 编程部分 -->
+        <div 
+          class="middle-box"
+          ref="middleBox"
+          @mousedown="handleMouseDown"
+          @mousemove="handleMouseMove"
+          @mouseup="handleMouseUp"
+          @mouseleave="handleMouseLeave"
+        >
+          <div 
+            v-for="(course, index) in courseCategories" 
+            :key="index" 
+            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="box-title">
+                <span>{{ course.title }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 底部切换按钮 -->
+        <div class="bottom-box">
+          <div class="line-container">
+            <div class="bold-line"></div>
+            <div 
+              v-for="(button, index) in circleButtons" 
+              :key="index" 
+              class="circle-button"
+              :class="{ 'active': activeButton === index }"
+              @click="activeButton = index"
+            >
+            </div>
+          </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
+// 返回图标
+import { ArrowLeftBold } from '@element-plus/icons-vue';
+// 导入路由
+import { useRouter } from 'vue-router';
+
+// 创建路由实例
+const router = useRouter()
+
+// 跳转到编程课程列表页
+const goToProgrammingList = (course, index) => {
+  // 跳转到programminglist页面,并传递课程信息作为参数
+  router.push({
+    path: '/programminglist',
+    query: {
+      courseIndex: index,
+      courseTitle: course.title
+    }
+  })
+}
+
+// 导入背景图片
+import bg01 from '@/assets/programming/bg01.png'
+import bg02 from '@/assets/programming/bg02.png'
+import bg03 from '@/assets/programming/bg03.png'
+import bg04 from '@/assets/programming/bg04.png'
+import bg05 from '@/assets/programming/bg05.png'
+import bg06 from '@/assets/programming/bg06.png'
+
+
+// 定义课程类别数据
+const courseCategories = reactive([
+  { title: '初始编程', bgImage: bg01 },
+  { title: '顺序编程', bgImage: bg02 },
+  { title: '编程挑战', bgImage: bg03 },
+  { title: '参数编程', bgImage: bg04 },
+  { title: '循环编程', bgImage: bg05 },
+  { title: '条件编程', bgImage: bg06 }
+])
+
+// 当前激活的按钮索引
+const activeButton = ref(0)
+
+// 定义圆形按钮数据(根据课程类别数量动态生成)
+const circleButtons = reactive(courseCategories.map((_, index) => ({ text: String(index + 1) })))
+
+// 自动滚动到中间位置
+watch(activeButton, (newIndex) => {
+  if (middleBox.value) {
+    // 找到对应的课程卡片元素
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 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 isDragging = ref(false)
+const startX = ref(0)
+const scrollLeft = ref(0)
+
+// 鼠标按下事件处理函数
+const handleMouseDown = (e) => {
+  isDragging.value = true
+  startX.value = e.pageX - middleBox.value.offsetLeft
+  scrollLeft.value = middleBox.value.scrollLeft
+}
+
+// 鼠标移动事件处理函数
+const handleMouseMove = (e) => {
+  if (!isDragging.value) return
+  e.preventDefault()
+  const x = e.pageX - middleBox.value.offsetLeft
+  const walk = (x - startX.value) * 2 // 滚动速度
+  middleBox.value.scrollLeft = scrollLeft.value - walk
+}
+
+// 鼠标松开事件处理函数
+const handleMouseUp = () => {
+  isDragging.value = false
+}
+
+// 鼠标离开事件处理函数
+const handleMouseLeave = () => {
+  isDragging.value = false
+}
+
+// 获取middleBox元素的引用
+const middleBox = ref(null)
+
+
+
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+.programming-content{
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-image: url('@/assets/programming/list_bg.png');
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  display: flex;
+  flex-direction: column;
+}
+.top-box {
+  height: 20%;
+  display: flex;
+}
+.top-left-box,
+.top-right-box {
+  flex: 1;
+  height: 50%;
+  border-radius: rpx(5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.top-center-box {
+  flex: 2;
+  border-radius: rpx(5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.top-left-inner-box,
+.top-center-inner-box,
+.top-right-inner-box {
+  width: 100%;
+  height: 100%;
+}
+.top-center-inner-box{
+    background-size: 70%; 
+    background-position: 50% 80%;
+    background-repeat: no-repeat;
+    display: flex; /* 使用flex布局 */
+    align-items: center; /* 垂直居中 */
+    justify-content: center; /* 水平居中 */
+    span{
+        font-size: rpx(17);
+        color: white;
+    }
+  }
+.top-left-inner-box{
+  display: flex;
+  align-items: center; /* 垂直居中对齐 */
+  .left-icon{
+    font-size: rpx(14);
+    color: white;
+    padding-left: rpx(20);
+    cursor: pointer;
+  }
+  .left-text{
+    font-size: rpx(14);
+    color: white;
+    padding-left: rpx(10);
+    cursor: pointer;
+  }
+}
+  
+.middle-box {
+  height: 60%;
+  overflow-x: auto; /* 水平滚动条 */
+  overflow-y: hidden; /* 取消上下滚动 */
+  white-space: nowrap; /* 防止元素换行 */
+  padding: 0 rpx(20); /* 左右内边距 */
+  scroll-behavior: smooth; /* 平滑滚动效果 */
+  -webkit-overflow-scrolling: touch; /* 触摸设备支持 */
+  position: relative;
+}
+
+.middle-inner-box{
+  width: rpx(200); /* 设置固定宽度 */
+  height: 100%; /* 设置固定高度 */
+  position: relative;
+  cursor: grab; /* 显示可抓取图标 */
+  user-select: none; /* 禁止文本选择 */
+  display: inline-block; /* 水平排列 */
+  margin-right: rpx(20); 
+  vertical-align: middle; /* 垂直居中对齐 */
+}
+
+/* 鼠标按下时的光标样式 */
+.middle-inner-box:active {
+  cursor: grabbing;
+}
+
+/* 隐藏滚动条 */
+.middle-box::-webkit-scrollbar {
+  height: rpx(0);
+}
+
+.middle-inner-box{
+  width: rpx(200); /* 设置固定宽度 */
+  height: 100%; /* 设置固定高度 */
+  position: relative;
+  cursor: pointer;
+  display: inline-block; /* 水平排列 */
+  margin-right: rpx(20); 
+  vertical-align: middle; /* 垂直居中对齐 */
+  touch-action: none; /* 防止触摸设备上的默认行为 */
+}
+
+.child-box-with-bg {
+  width: 100%; 
+  height: 100%;
+  background-size: 105%; 
+  background-position: center; 
+  background-repeat: no-repeat;
+  transition: all 0.3s ease; /* 过渡效果 */
+}
+
+/* 鼠标悬浮时的放大效果 */
+.child-box-with-bg:hover {
+  transform: scale(1.1); /* 放大1.1倍 */
+  z-index: 10; /* 确保放大时在顶层显示 */
+}
+
+/* 选中时的放大效果 */
+.child-box-with-bg.active {
+  transform: scale(1.1); /* 放大1.1倍 */
+  z-index: 10; /* 确保放大时在顶层显示 */
+}
+
+
+/* 标题样式 */
+.box-title {
+  position: absolute;
+  top: 15%; /* 百分比定位,相对于容器 */
+  left: 0;
+  right: 0;
+  color: white;
+  text-align: center;
+  font-size: rpx(18); 
+  letter-spacing: rpx(6); /* 增加字体间距 */
+}
+
+
+.bottom-box {
+  height: 20%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.line-container {
+  width: 80%;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.bold-line {
+  position: absolute;
+  width: 100%;
+  height: rpx(5);
+  background-color: rgba(37, 115, 188, 0.5);
+  border-radius: rpx(3);
+}
+
+.circle-button {
+  width: rpx(10);
+  height: rpx(10);
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255);
+  border: rpx(1) solid #00c1fc;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  z-index: 1;
+}
+.circle-button:hover {
+  background-color: white;
+  transform: scale(1.5); // 放大效果
+}
+.circle-button.active {
+  background-color: #f9df04;
+  border: rpx(1.5) solid #00c1fc;
+  color: white;
+  transform: scale(2);
+  box-shadow: 0 0 rpx(10) rgba(0, 0, 0, 0.5);
+}
+
+</style>

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

@@ -0,0 +1,438 @@
+<!-- 编程课列表 -->
+<template>
+    <div class="programming-content">
+      <!-- 标题部分 -->
+      <div class="top-box">
+        <div class="top-left-box">
+          <div class="top-left-inner-box">
+            <!-- 左侧返回图标 -->
+             <el-icon class="left-icon" @click="goBackIndex"><ArrowLeftBold /></el-icon>
+             <span class="left-text">AI编程课</span>
+          </div>
+        </div>
+        <div class="top-center-box">
+          <div class="top-center-inner-box" style="background-image: url('./src/assets/programming/list_title.png');">
+            <span>{{ pageTitle }}</span>
+          </div>
+        </div>
+        <div class="top-right-box">
+          <div class="top-right-inner-box"></div>
+        </div>
+      </div>
+
+        <!-- 编程部分 -->
+        <div 
+          class="middle-box"
+          ref="middleBox"
+          @mousedown="handleMouseDown"
+          @mousemove="handleMouseMove"
+          @mouseup="handleMouseUp"
+          @mouseleave="handleMouseLeave"
+        >
+          <div 
+            v-for="(course, index) in specificCourses" 
+            :key="index" 
+            class="middle-inner-box"
+            @click="goToProgrammingList(course, index)"
+          >
+            <div class="new-white-box" :class="{ 'active': activeButton === index }" @click="goToProgrammingList(course, index)">
+              <div class="bg-image-container" :style="{ backgroundImage: `url(${course.bgImage})` }"></div>
+              <div class="text-container">
+                <div class="box-title">
+                  <span>{{ course.title }}</span>
+                </div>
+              </div>
+              <!-- unlock背景图盒子 -->
+              <div class="unlock-box" :style="{ backgroundImage: `url(${unlockImage})` }"></div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 底部切换按钮 -->
+        <div class="bottom-box">
+          <div class="line-container">
+            <div class="bold-line"></div>
+            <div 
+              v-for="(button, index) in circleButtons" 
+              :key="index" 
+              class="circle-button"
+              :class="{ 'active': activeButton === index }"
+              @click="activeButton = index"
+            >
+            </div>
+          </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
+// 返回图标
+import { ArrowLeftBold } from '@element-plus/icons-vue';
+// 导入路由
+import { useRouter, useRoute } from 'vue-router';
+
+// 获取路由实例
+const router = useRouter()
+// 获取当前路由信息
+const route = useRoute()
+// 页面标题,默认为'AI编程课'
+const pageTitle = ref('AI编程课')
+
+// 返回上一页
+const goBackIndex = () => {
+  // 导航到ProgrammingGame页面
+  router.push('/programming02')
+}
+
+// 跳转到课程详情页面
+const goToProgrammingList = (course, index) => {
+  // 跳转ProgrammingCourset页面,并传递课程信息作为参数
+  router.push({
+    path: '/programmingCourset',
+    query: {
+      courseTitle: course.title,
+      courseIndex: index
+    }
+  })
+}
+
+
+// 导入背景图片
+import list_img01 from '@/assets/programming/list_img01.png'
+import list_img02 from '@/assets/programming/list_img02.png'
+import list_img03 from '@/assets/programming/list_img03.png'
+import unlockImage from '@/assets/programming/unlock.png'
+
+
+// 定义课程数据
+const specificCourses = reactive([
+  { title: '汽车世界', bgImage: list_img01 },
+  { title: '汽车制造', bgImage: list_img02 },
+  { title: '红绿灯', bgImage: list_img03 },
+  { title: '汽车世界', bgImage: list_img01 },
+  { title: '汽车制造', bgImage: list_img02 },
+  { title: '红绿灯', bgImage: list_img03 },
+])
+
+// 当前激活的按钮索引
+const activeButton = ref(0)
+
+// 定义圆形按钮数据(根据课程数量动态生成)
+const circleButtons = reactive(specificCourses.map((_, index) => ({ text: String(index + 1) })))
+
+// 自动滚动到中间位置
+watch(activeButton, (newIndex) => {
+  if (middleBox.value) {
+    // 找到对应的课程卡片元素
+    const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 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 isDragging = ref(false)
+const startX = ref(0)
+const scrollLeft = ref(0)
+
+// 鼠标按下事件处理函数
+const handleMouseDown = (e) => {
+  isDragging.value = true
+  startX.value = e.pageX - middleBox.value.offsetLeft
+  scrollLeft.value = middleBox.value.scrollLeft
+}
+
+// 鼠标移动事件处理函数
+const handleMouseMove = (e) => {
+  if (!isDragging.value) return
+  e.preventDefault()
+  const x = e.pageX - middleBox.value.offsetLeft
+  const walk = (x - startX.value) * 2 // 滚动速度
+  middleBox.value.scrollLeft = scrollLeft.value - walk
+}
+
+// 鼠标松开事件处理函数
+const handleMouseUp = () => {
+  isDragging.value = false
+}
+
+// 鼠标离开事件处理函数
+const handleMouseLeave = () => {
+  isDragging.value = false
+}
+
+// 获取middleBox元素的引用
+const middleBox = ref(null)
+
+// 组件挂载时获取路由参数设置标题
+onMounted(() => {
+  // 检查路由参数中是否有courseTitle
+  const courseTitle = route.query.courseTitle
+  if (courseTitle) {
+    // 如果有courseTitle参数,则设置页面标题
+    pageTitle.value = courseTitle
+  }
+})
+
+
+
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+.programming-content{
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-image: url('@/assets/programming/list_bg02.png');
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  display: flex;
+  flex-direction: column;
+}
+.top-box {
+  height: 20%;
+  display: flex;
+}
+.top-left-box,
+.top-right-box {
+  flex: 1;
+  height: 50%;
+  border-radius: rpx(5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.top-center-box {
+  flex: 2;
+  border-radius: rpx(5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.top-left-inner-box,
+.top-center-inner-box,
+.top-right-inner-box {
+  width: 100%;
+  height: 100%;
+}
+.top-center-inner-box{
+    background-size: 70%; 
+    background-position: 50% 80%;
+    background-repeat: no-repeat;
+    display: flex; /* 使用flex布局 */
+    align-items: center; /* 垂直居中 */
+    justify-content: center; /* 水平居中 */
+    span{
+        font-size: rpx(17);
+        color: white;
+    }
+  }
+.top-left-inner-box{
+  display: flex;
+  align-items: center; /* 垂直居中对齐 */
+  .left-icon{
+    font-size: rpx(14);
+    color: white;
+    padding-left: rpx(20);
+    cursor: pointer;
+  }
+  .left-text{
+    font-size: rpx(14);
+    color: white;
+    padding-left: rpx(10);
+    cursor: pointer;
+  }
+}
+  
+.middle-box {
+  height: 60%;
+  overflow-x: auto; /* 水平滚动条 */
+  overflow-y: hidden; /* 取消上下滚动 */
+  white-space: nowrap; /* 防止元素换行 */
+  padding: 0 rpx(20); /* 左右内边距 */
+  scroll-behavior: smooth; /* 平滑滚动效果 */
+  -webkit-overflow-scrolling: touch; /* 触摸设备支持 */
+  position: relative;
+}
+
+.middle-inner-box{
+  width: rpx(200); /* 设置固定宽度 */
+  height: 100%; /* 设置固定高度 */
+  position: relative;
+  cursor: grab; /* 显示可抓取图标 */
+  user-select: none; /* 禁止文本选择 */
+  display: inline-block; /* 水平排列 */
+  margin-right: rpx(10); 
+  vertical-align: middle; /* 垂直居中对齐 */
+}
+
+/* 鼠标按下时的光标样式 */
+.middle-inner-box:active {
+  cursor: grabbing;
+}
+
+/* 隐藏滚动条 */
+.middle-box::-webkit-scrollbar {
+  height: rpx(0);
+}
+
+.middle-inner-box{
+  width: rpx(130); /* 设置固定宽度 */
+  height: 100%; /* 设置固定高度 */
+  position: relative;
+  cursor: pointer;
+  display: inline-block; /* 水平排列 */
+  margin-right: rpx(20); 
+  margin-left: rpx(20); 
+  vertical-align: middle; /* 垂直居中对齐 */
+  touch-action: none; /* 防止触摸设备上的默认行为 */
+}
+
+.new-white-box {
+  width: 100%; 
+  height: 65%;
+  margin-top: rpx(20);
+  background-color: white;
+  border-radius: rpx(15);
+  display: flex;
+  flex-direction: column;
+  transition: all 0.3s ease; /* 过渡效果 */
+  position: relative;
+}
+
+/* 小三角效果 */
+.new-white-box::after {
+  content: '';
+  position: absolute;
+  bottom: -rpx(8);
+  left: 50%;
+  transform: translateX(-50%);
+  width: 0;
+  height: 0;
+  top: rpx(150);
+  border-left: rpx(10) solid transparent;
+  border-right: rpx(10) solid transparent;
+  border-top: rpx(10) solid white;
+}
+
+/* 鼠标悬浮/选中时的放大效果 */
+.new-white-box:hover,
+.new-white-box.active {
+  transform: scale(1.1); /* 放大1.1倍 */
+  z-index: 10; /* 确保放大时在顶层显示 */
+  border: rpx(5) solid #D0D7F7; /* 边框效果 */
+  box-shadow: 0 0 rpx(10) rgba(0, 0, 0, 0.5);
+}
+
+
+
+/* 背景图容器 */
+.bg-image-container {
+  width: 100%;
+  height: 85%; 
+  background-size: 105%; 
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+/* 文字容器 */
+.text-container {
+  width: 100%;
+  height: 15%; 
+  display: flex;
+  line-height: rpx(10);
+  justify-content: center;
+}
+
+/* 标题样式 */
+.box-title {
+  color: #333;
+  text-align: center;
+  font-size: rpx(11); 
+  color: #45300b;
+}
+
+/* unlock盒子样式 */
+.unlock-box {
+  position: absolute;
+  width: 100%;
+  height: 30%;
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+  // bottom: -rpx(20);
+  left: 50%;
+  transform: translateX(-50%);
+  top: rpx(149);
+  z-index: 5;
+}
+.bottom-box {
+  height: 20%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.line-container {
+  width: 80%;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.bold-line {
+  position: absolute;
+  width: 100%;
+  height: rpx(5);
+  background-color: rgba(37, 115, 188, 0.5);
+  border-radius: rpx(3);
+}
+
+.circle-button {
+  width: rpx(10);
+  height: rpx(10);
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255);
+  border: rpx(1) solid #00c1fc;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  z-index: 1;
+}
+.circle-button:hover {
+  background-color: white;
+  transform: scale(1.5); // 放大效果
+}
+.circle-button.active {
+  background-color: #f9df04;
+  border: rpx(1.5) solid #00c1fc;
+  color: white;
+  transform: scale(2);
+  box-shadow: 0 0 rpx(10) rgba(0, 0, 0, 0.5);
+}
+
+</style>