Jelajahi Sumber

Merge remote-tracking branch 'origin/wanzi' into muzi

# Conflicts:
#	src/assets/programming/trophy.png
#	src/assets/programming/unlock.png
#	src/views/programming/ProgrammingList.vue
liyanbo 4 bulan lalu
induk
melakukan
79d4e56a2c

+ 6 - 6
src/api/programming/index.js

@@ -1,8 +1,8 @@
 import axios from '@/utils/request'
 
 
-// 获取课程主题
-export function CategoryList (data) {
+// 获取主题列表
+export function getThemeList (data) {
   return axios({
     url: 'bjdxWeb/blockly/getTypeTheme',
     method: 'get',
@@ -10,16 +10,16 @@ export function CategoryList (data) {
   })
 }
 
-// 获取课程主题列表
-export function TopicList (id) {
+// 获取类型列表
+export function getTypeByThemeId (id) {
   return axios({
     url: 'bjdxWeb/blockly/getTypeByThemeId?id=' + id,
     method: 'get'
   })
 }
 
-// 根据ID获取列表信息
-export function TopicType (typeId) {
+// 根据ID获取课程列表
+export function getBlocklyByTypeId (typeId) {
   return axios({
     url: 'bjdxWeb/blockly/getBlocklyByTypeId?typeId=' + typeId,
     method: 'get' 

TEMPAT SAMPAH
src/assets/programming/list_bg04.png


TEMPAT SAMPAH
src/assets/programming/star01.png


TEMPAT SAMPAH
src/assets/programming/star02.png


TEMPAT SAMPAH
src/assets/programming/track01.png


+ 1 - 2
src/components/HomePage.vue

@@ -390,6 +390,7 @@ window.updateTenantName = (newName) => {
   display: flex;
   flex: 1;
   padding-left: rpx(30);
+    cursor: pointer;
 }
 
 .left-box span {
@@ -400,8 +401,6 @@ window.updateTenantName = (newName) => {
   font-size: rpx(11);
   color: white;
   max-width: rpx(200); // 最大宽度限制
-  // overflow: hidden;
-  // text-overflow: ellipsis;
   white-space: normal; // 允许换行
   line-height: rpx(16); // 行高
   text-align: left;

+ 3 - 3
src/views/programming/Interface.vue

@@ -140,7 +140,7 @@ import TextToImage from "@/components/ai/image/TextToImage.vue";
 import ImageToImage from "@/components/ai/image/ImageToImage.vue";
 import ImageToVideo from "@/components/ai/video/ImageToVideo.vue";
 import MapGame from "@/components/blockly/MapGame.vue";
-import {TopicType} from "@/api/programming/index.js";
+import {getBlocklyByTypeId} from "@/api/programming/index.js";
 
 const router = useRouter() // 获取当前路由对象
 // 渲染页面标题
@@ -414,8 +414,8 @@ onMounted(async () => {
   if (typeIdParam) {
     typeId.value = typeIdParam;
     try {
-
-      TopicType(typeIdParam).then(res => {
+      // 获取课程列表
+      getBlocklyByTypeId(typeIdParam).then(res => {
         if (res && res.data && Array.isArray(res.data)) {
           props.courseList = res.data
           // 保存原始API返回的数据

+ 101 - 33
src/views/programming/ProgrammingCourset.vue

@@ -26,31 +26,42 @@
     </div>
 
     <!-- 课程部分 -->
-    <div class="lower-box">
-      <div class="content-box">
+    <div class="lower-box" v-if="!showVideo">
+        <!-- 轨道图片 -->
+         <img src="@/assets/programming/track01.png" class="background-img" alt="背景图" />
         <!-- 左切换按钮 -->
-        <div v-if="!showVideo" class="carousel-btn prev-btn" @click="prevSlide" :disabled="currentIndex === 0">
-          <el-icon class="btn-icon"><ArrowLeftBold /></el-icon>
+        <div v-if="!showVideo && courseItems.length > 3" class="carousel-btn prev-btn" @click="prevSlide" :class="{ 'disabled-btn': currentIndex === 0 }" :disabled="currentIndex === 0">
+          <el-icon class="btn-icon" :class="{ 'disabled-icon': currentIndex === 0 }"><ArrowLeftBold /></el-icon>
         </div>
-         
-        <!-- 动态渲染课程内容 -->
-        <div 
-          v-for="(item, index) in displayItems" 
-          :key="item.id"
-          :class="getPositionClass(index)"
-          @click="handleCourseItemClick(item)"
-        >
-          <div class="box-content">
-            <img :src="item.image" :alt="item.title" class="box-image" />
-            <div class="box-text">{{ item.title }}</div>
+        <div class="content-box">
+          <!-- 动态渲染课程内容 -->
+          <div 
+            v-for="(item, index) in displayItems" 
+            :key="item.id"
+            :class="getPositionClass(index)"
+            @click="handleCourseItemClick(item)"
+          >
+            <div class="box-content">
+              <img :src="item.image" :alt="item.title" class="box-image" />
+              <div class="box-text">{{ item.title }}</div>
+            </div>
+            <!-- 星星图标组 -->
+            <div class="star-group">
+              <div 
+                v-for="i in getStarCount(item.contentType)" 
+                :key="i"
+                class="star-icon"
+                :style="{ 
+                  backgroundImage: `url(${item.progress ? star01Image : star02Image})`
+                }"
+              ></div>
+            </div>
           </div>
         </div>
-        
         <!-- 右切换按钮 -->
-        <div v-if="!showVideo" class="carousel-btn next-btn" @click="nextSlide" :disabled="currentIndex >= courseItems.length - 3">
-          <el-icon class="btn-icon"><ArrowRightBold /></el-icon>
+        <div v-if="!showVideo && courseItems.length > 3" class="carousel-btn next-btn" @click="nextSlide" :class="{ 'disabled-btn': currentIndex >= courseItems.length - 3 }" :disabled="currentIndex >= courseItems.length - 3">
+          <el-icon class="btn-icon" :class="{ 'disabled-icon': currentIndex >= courseItems.length - 3 }"><ArrowRightBold /></el-icon>
         </div>
-      </div>
     </div>
     
     <!-- 视频和编程界面 -->
@@ -65,8 +76,8 @@ import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
 import { ref, onMounted, computed } from 'vue';
 // 导入路由
 import { useRouter, useRoute } from 'vue-router';
-// 导入接口
-import { TopicType } from '@/api/programming/index.js'
+// 根据ID获取课程列表
+import { getBlocklyByTypeId } from '@/api/programming/index.js'
 // 导入图片
 import  explanation  from '@/assets/programming/explanation.png'
 import  practice  from '@/assets/programming/practice.png'
@@ -74,6 +85,10 @@ import  summary  from '@/assets/programming/summary.png'
 
 import Interface from './Interface.vue'
 
+// 星星图片
+import star02Image from '@/assets/programming/star02.png'
+import star01Image from '@/assets/programming/star01.png'
+
 
 // 获取路由实例
 const router = useRouter()
@@ -96,6 +111,16 @@ const resData = ref([])
 // 轮播图当前索引
 const currentIndex = ref(0)
 
+// 根据内容类型获取星星数量
+const getStarCount = (contentType) => {
+  if (contentType === 'video') {
+    return 1; // video类型渲染1个星星
+  } else if (contentType === 'blockly') {
+    return 3; // blockly类型渲染3个星星
+  }
+  return 0; // 其他类型默认不渲染星星
+}
+
 // 显示的项目(每次显示3个)
 const displayItems = computed(() => {
   return courseItems.value.slice(currentIndex.value, currentIndex.value + 3)
@@ -136,11 +161,12 @@ onMounted(() => {
   originalCourseId.value = route.query.originalCourseId
   originalCourseTitle.value = route.query.originalCourseTitle
   
-  // 获取到topicId后,再调用TopicType接口
+  // 获取到topicId后,再获取课程列表getBlocklyByTypeId接口
   if (typeId.value) {
-    
-    TopicType(typeId.value).then(res => {
+    getBlocklyByTypeId(typeId.value).then(res => {
       if (res && res.data && Array.isArray(res.data)) {
+        console.log(res);
+        
         // 保存原始API返回的数据
         resData.value = res.data;
         // 创建图片映射,根据bcLabel显示对应图片
@@ -158,7 +184,8 @@ onMounted(() => {
             id: item.id,
             title: item.bcName,
             image: image,
-            contentType: item.bcContentType
+            contentType: item.bcContentType,
+            progress: item.progress 
           })
         })
       }
@@ -244,6 +271,8 @@ const goBackIndex = () => {
   display: flex; 
   align-items: center; 
   justify-content: center; 
+      cursor: pointer;
+
   span{
       font-size: rpx(15);
       color: white;
@@ -286,6 +315,7 @@ const goBackIndex = () => {
     justify-content: center;
     color: #45300b;
     font-size: rpx(10);
+    cursor: pointer;
   }
 }
 
@@ -299,15 +329,22 @@ const goBackIndex = () => {
 }
 
 .content-box {
-  width: 100%;
+  min-width: rpx(660);
   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;
+  overflow: hidden;
+}
+
+.background-img {
+  position: absolute;
+  width: 150%;
+  height: 60%;
+  left:  -10%;
+  top: 25%;
+  object-fit: cover;
+  z-index: 1;
 }
 
 .left-content-box, .center-content-box, .right-content-box {
@@ -322,6 +359,7 @@ const goBackIndex = () => {
   box-shadow: 0 rpx(5) rpx(10) rgba(0, 0, 0, 0.2);
   transition: transform 0.3s ease;
   cursor: pointer;
+  z-index: 2; /* 确保内容在背景图上方 */
 }
 
 /* 位置调整 */
@@ -380,10 +418,31 @@ const goBackIndex = () => {
   text-align: center;
 }
 
+/* 星星图标样式 */
+.star-group {
+  position: absolute;
+  top: 100%;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 3;
+}
+
+.star-icon {
+  position: relative;
+  width: rpx(23);
+  height: rpx(23);
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+  margin: 0 rpx(0);
+}
+
 /* 轮播图按钮样式 */
 .carousel-btn {
   position: absolute;
-  top: 40%;
+  top: 50%;
   transform: translateY(-50%);
   width: rpx(30);
   height: rpx(30);
@@ -398,14 +457,23 @@ const goBackIndex = () => {
   transition: all 0.3s ease;
 }
 
-.carousel-btn:hover {
+.carousel-btn:hover:not(.disabled-btn) {
   background-color: rgba(255, 255, 255, 1);
   transform: translateY(-50%) scale(1.1);
 }
 
-.carousel-btn:disabled {
+.carousel-btn:disabled,
+.disabled-btn {
   opacity: 0.5;
   cursor: not-allowed;
+  background-color: rgba(200, 200, 200, 0.7);
+  box-shadow: none;
+}
+
+.disabled-icon {
+  color: #ccc !important;
+  filter: grayscale(100%);
+  opacity: 0.6;
 }
 
 .prev-btn {

+ 140 - 39
src/views/programming/ProgrammingGame.vue

@@ -21,26 +21,38 @@
       </div>
 
         <!-- 编程部分 -->
-        <div 
-          class="middle-box"
-          ref="middleBox"
-          @mousedown="handleMouseDown"
-          @mousemove="handleMouseMove"
-          @mouseup="handleMouseUp"
-          @mouseleave="handleMouseLeave"
-        >
+        <div class="middle-wrapper">
+          <!-- 左切换按钮 -->
+          <div class="carousel-btn prev-btn" @click="prevSlide" :class="{ 'disabled-btn': currentIndex === 0 }" :disabled="currentIndex === 0">
+            <el-icon class="btn-icon" :class="{ 'disabled-icon': currentIndex === 0 }"><ArrowLeftBold /></el-icon>
+          </div>
+          
           <div 
-            v-for="(course, index) in courseCategories" 
-            :key="index" 
-            class="middle-inner-box"
-            @click.stop="activeButton = index"
+            class="middle-box"
+            ref="middleBox"
+            @mousedown="handleMouseDown"
+            @mousemove="handleMouseMove"
+            @mouseup="handleMouseUp"
+            @mouseleave="handleMouseLeave"
           >
-            <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 
+              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="carousel-btn next-btn" @click="nextSlide" :class="{ 'disabled-btn': currentIndex >= courseCategories.length - 3 }" :disabled="currentIndex >= courseCategories.length - 3">
+            <el-icon class="btn-icon" :class="{ 'disabled-icon': currentIndex >= courseCategories.length - 3 }"><ArrowRightBold /></el-icon>
+          </div>
         </div>
 
         <!-- 底部切换按钮 -->
@@ -61,13 +73,13 @@
 </template>
 
 <script setup>
-import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
+import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue'
 // 返回图标
-import { ArrowLeftBold } from '@element-plus/icons-vue';
+import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
 // 导入路由
 import { useRouter } from 'vue-router';
 // 导入课程类别接口
-import { CategoryList } from '@/api/programming/index.js'
+import { getThemeList } from '@/api/programming/index.js'
 
 // 创建路由实例
 const router = useRouter()
@@ -77,9 +89,11 @@ const activeButton = ref(0)
 const circleButtons = reactive([])
 // 定义课程类别数据
 const courseCategories = reactive([])
+// 轮播图当前索引
+const currentIndex = ref(0)
 
-// 获取课程类别
-CategoryList().then(res => {
+// 获取主题列表
+getThemeList().then(res => {
   console.log(res);
   if (res && res.data) {
     // 清空现有数据
@@ -154,6 +168,41 @@ const handleMouseLeave = () => {
 // 获取middleBox元素的引用
 const middleBox = ref(null)
 
+// 上一页
+const prevSlide = () => {
+  if (currentIndex.value > 0) {
+    currentIndex.value = Math.max(0, currentIndex.value - 3)
+    // 滚动到对应位置
+    if (middleBox.value) {
+      const scrollPosition = currentIndex.value * (rpxValue(200) + rpxValue(20));
+      middleBox.value.scrollTo({
+        left: scrollPosition,
+        behavior: 'smooth'
+      });
+    }
+  }
+}
+
+// 下一页
+const nextSlide = () => {
+  if (currentIndex.value < courseCategories.length - 3) {
+    currentIndex.value = Math.min(courseCategories.length - 3, currentIndex.value + 3)
+    // 滚动到对应位置
+    if (middleBox.value) {
+      const scrollPosition = currentIndex.value * (rpxValue(200) + rpxValue(20));
+      middleBox.value.scrollTo({
+        left: scrollPosition,
+        behavior: 'smooth'
+      });
+    }
+  }
+}
+
+// rpx转换为像素值的辅助函数
+const rpxValue = (px) => {
+  return (px / 750) * window.innerWidth;
+}
+
 
 
 // 跳转到编程课程列表页
@@ -230,6 +279,7 @@ const goToHomePage = () => {
     display: flex; /* 使用flex布局 */
     align-items: center; /* 垂直居中 */
     justify-content: center; /* 水平居中 */
+      cursor: pointer;
     span{
         font-size: rpx(17);
         color: white;
@@ -252,26 +302,37 @@ const goToHomePage = () => {
   }
 }
   
-.middle-box {
+.middle-wrapper {
   height: 60%;
+  overflow: hidden; /* 溢出隐藏 */
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.middle-box {
+  min-width: rpx(650); /* 固定最小宽度 */
+  height: 100%;
+  margin: 0 rpx(50);
   overflow-x: auto; /* 水平滚动条 */
   overflow-y: hidden; /* 取消上下滚动 */
   white-space: nowrap; /* 防止元素换行 */
-  padding: 0 rpx(20); /* 左右内边距 */
   scroll-behavior: smooth; /* 平滑滚动效果 */
   -webkit-overflow-scrolling: touch; /* 触摸设备支持 */
   position: relative;
+  flex: 1;
 }
 
 .middle-inner-box{
   width: rpx(200); /* 设置固定宽度 */
   height: 100%; /* 设置固定高度 */
   position: relative;
-  cursor: grab; /* 显示可抓取图标 */
+      cursor: pointer;
   user-select: none; /* 禁止文本选择 */
   display: inline-block; /* 水平排列 */
-  margin-right: rpx(20); 
   vertical-align: middle; /* 垂直居中对齐 */
+  margin-right: rpx(15); /* 增加卡片之间的间距 */
 }
 
 /* 鼠标按下时的光标样式 */
@@ -284,21 +345,11 @@ const goToHomePage = () => {
   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-size: 100%; 
   background-position: center; 
   background-repeat: no-repeat;
   transition: all 0.3s ease; /* 过渡效果 */
@@ -338,7 +389,7 @@ const goToHomePage = () => {
 }
 
 .line-container {
-  width: 80%;
+  width: 65%;
   position: relative;
   display: flex;
   align-items: center;
@@ -354,8 +405,8 @@ const goToHomePage = () => {
 }
 
 .circle-button {
-  width: rpx(10);
-  height: rpx(10);
+  width: rpx(7);
+  height: rpx(7);
   border-radius: 50%;
   background-color: rgba(255, 255, 255);
   border: rpx(1) solid #00c1fc;
@@ -372,10 +423,60 @@ const goToHomePage = () => {
 }
 .circle-button.active {
   background-color: #f9df04;
-  border: rpx(1.5) solid #00c1fc;
+  border: rpx(1) solid #00c1fc;
   color: white;
   transform: scale(2);
   box-shadow: 0 0 rpx(10) rgba(0, 0, 0, 0.5);
 }
 
+/* 轮播图按钮样式 */
+.carousel-btn {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: rpx(30);
+  height: rpx(30);
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  z-index: 10;
+  box-shadow: 0 rpx(5) rpx(10) rgba(0, 0, 0, 0.2);
+  transition: all 0.3s ease;
+}
+
+.carousel-btn:hover:not(.disabled-btn) {
+  background-color: rgba(255, 255, 255, 1);
+  transform: translateY(-50%) scale(1.1);
+}
+
+.carousel-btn:disabled,
+.disabled-btn {
+  opacity: 0.5;
+  cursor: not-allowed;
+  background-color: rgba(200, 200, 200, 0.7);
+  box-shadow: none;
+}
+
+.disabled-icon {
+  color: #ccc !important;
+  filter: grayscale(100%);
+  opacity: 0.6;
+}
+
+.prev-btn {
+  left: 2%;
+}
+
+.next-btn {
+  right: 2%;
+}
+
+.btn-icon {
+  font-size: rpx(15);
+  color: #333;
+}
+
 </style>

+ 177 - 53
src/views/programming/ProgrammingList.vue

@@ -21,35 +21,60 @@
       </div>
 
         <!-- 编程部分 -->
-        <div 
-          class="middle-box"
-          ref="middleBox"
-          @mousedown="handleMouseDown"
-          @mousemove="handleMouseMove"
-          @mouseup="handleMouseUp"
-          @mouseleave="handleMouseLeave"
-        >
-          <div
-              v-for="(courseType, index) in specificCourses"
-              :key="index"
-              class="middle-inner-box"
-              @click="goToProgrammingList(courseType, index)"
-          >
-            <div class="new-white-box"
-                 :class="{ 'active': activeButton === index, 'disabled': courseType.isDisabled }"
-                 @click="goToProgrammingList(courseType, index)">
-              <div class="bg-image-container" :style="{ backgroundImage: `url(${courseType.bgImage})` }"></div>
-              <div class="text-container">
-                <div class="box-title">
-                  <span>{{ courseType.title }}</span>
+         <div class="middle-wrapper">
+          <!-- 左切换按钮 -->
+          <div class="carousel-btn prev-btn" @click="prevSlide" :class="{ 'disabled-btn': currentIndex === 0 }" :disabled="currentIndex === 0">
+            <el-icon class="btn-icon" :class="{ 'disabled-icon': currentIndex === 0 }"><ArrowLeftBold /></el-icon>
+          </div>
+
+            <div
+              class="middle-box"
+              ref="middleBox"
+              @mousedown="handleMouseDown"
+              @mousemove="handleMouseMove"
+              @mouseup="handleMouseUp"
+              @mouseleave="handleMouseLeave"
+            >
+              <div
+                v-for="(courseType, index) in specificCourses"
+                :key="index"
+                class="middle-inner-box"
+                @click="goToProgrammingList(courseType, index)"
+              >
+                <div class="new-white-box"
+                     :class="{ 'active': activeButton === index, 'disabled': courseType.isDisabled }"
+                     @click="goToProgrammingList(courseType, index)">
+                    <!-- 列表封面图 -->
+                  <div class="bg-image-container" :style="{ backgroundImage: `url(${courseType.bgImage})` }">
+                    <!-- 星星图标 -->
+                    <div
+                      v-for="starIndex in 5"
+                      :key="starIndex"
+                      class="star-icon"
+                      :style="{
+                        backgroundImage: `url(${starIndex <= courseType.progress ? star01Image : star02Image})`,
+                        left: `${30 + (starIndex - 1) * 30}px`
+                      }"
+                    ></div>
+                  </div>
+                  <div class="text-container">
+                    <div class="box-title">
+                      <span>{{ courseType.title }}</span>
+                    </div>
+                  </div>
+                  <!-- unlock背景图盒子 -->
+                  <div class="unlock-box" :style="{ backgroundImage: courseType.isDisabled ? `url(${lockImage})` : courseType.progress > 0 ? `url(${trophyImage})` : `url(${unlockImage})` }"></div>
                 </div>
               </div>
-              <!-- unlock背景图盒子 -->
-              <div class="unlock-box" :style="{ backgroundImage: courseType.isDisabled ? `url(${lockImage})` : courseType.progress > 0 ? `url(${trophyImage})` : `url(${unlockImage})` }"></div>
             </div>
-          </div>
+
+        <!-- 右切换按钮 -->
+        <div class="carousel-btn next-btn" @click="nextSlide" :class="{ 'disabled-btn': currentIndex >= specificCourses.length - 3 }" :disabled="currentIndex >= specificCourses.length - 3">
+          <el-icon class="btn-icon" :class="{ 'disabled-icon': currentIndex >= specificCourses.length - 3 }"><ArrowRightBold /></el-icon>
         </div>
 
+         </div>
+
         <!-- 底部切换按钮 -->
         <div class="bottom-box">
           <div class="line-container">
@@ -70,15 +95,19 @@
 <script setup>
 import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
 // 返回图标
-import { ArrowLeftBold } from '@element-plus/icons-vue';
+import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue';
 // 导入路由
 import { useRouter, useRoute } from 'vue-router';
-// 导入主题列表接口
-import { TopicList } from '@/api/programming/index.js'
+// 获取类型列表
+import { getTypeByThemeId } from '@/api/programming/index.js'
 // 导入图片
 import lockImage from '@/assets/programming/lock.png'
 import unlockImage from '@/assets/programming/unlock.png'
 import trophyImage from '@/assets/programming/trophy.png'
+
+// 星星图片
+import star02Image from '@/assets/programming/star02.png'
+import star01Image from '@/assets/programming/star01.png'
 import {Message} from "@/utils/message/Message.js";
 
 // 获取路由实例
@@ -105,11 +134,13 @@ const specificCourses = reactive([])
 
 // 当前激活的按钮索引
 const activeButton = ref(0)
+// 轮播图当前索引
+const currentIndex = ref(0)
 
-// 在获取到categoryId后再调用TopicList接口
+// 在获取到categoryId后再调用获取类型列表getTypeByThemeId接口
 const fetchTopicList = () => {
   if (categoryId.value) {
-    TopicList(categoryId.value).then(res => {
+    getTypeByThemeId(categoryId.value).then(res => {
       console.log(categoryId.value, res);
       // 更新课程数据,使用接口返回的数据
       if (res && res.data && Array.isArray(res.data)) {
@@ -193,6 +224,36 @@ const handleMouseLeave = () => {
   isDragging.value = false
 }
 
+// 上一页
+const prevSlide = () => {
+  if (currentIndex.value > 0) {
+    currentIndex.value = Math.max(0, currentIndex.value - 3)
+    // 滚动到对应位置
+    if (middleBox.value) {
+      const scrollAmount = middleBox.value.clientWidth * 0.75
+      middleBox.value.scrollTo({
+        left: Math.max(0, middleBox.value.scrollLeft - scrollAmount),
+        behavior: 'smooth'
+      })
+    }
+  }
+}
+
+// 下一页
+const nextSlide = () => {
+  if (currentIndex.value < specificCourses.length - 3) {
+    currentIndex.value = Math.min(specificCourses.length - 3, currentIndex.value + 3)
+    // 滚动到对应位置
+    if (middleBox.value) {
+      const scrollAmount = middleBox.value.clientWidth * 0.75
+      middleBox.value.scrollTo({
+        left: middleBox.value.scrollLeft + scrollAmount,
+        behavior: 'smooth'
+      })
+    }
+  }
+}
+
 // 获取middleBox元素的引用
 const middleBox = ref(null)
 
@@ -202,7 +263,7 @@ onMounted(() => {
   if (localStorage.getItem("blocklyDataScope")) {
     blocklyDataScope.value = localStorage.getItem("blocklyDataScope").split(",");
   }
-  
+
   const title = route.query.courseTitle
   if (title) {
     pageTitle.value = title
@@ -211,7 +272,7 @@ onMounted(() => {
   const id = route.query.categoryId
   if (id) {
     categoryId.value = id
-    // 获取到categoryId后调用TopicList接口
+    // 获取到categoryId后调用getTypeByThemeId接口
     fetchTopicList()
   }
 })
@@ -335,25 +396,37 @@ const disableBlockly = (blocklyId = categoryId.value) => {
   }
 }
   
-.middle-box {
+.middle-wrapper {
   height: 60%;
+  overflow: hidden; /* 溢出隐藏 */
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.middle-box {
+  min-width: rpx(660); /* 固定最小宽度 */
+  height: 100%;
+  margin: 0 rpx(50);
   overflow-x: auto; /* 水平滚动条 */
   overflow-y: hidden; /* 取消上下滚动 */
   white-space: nowrap; /* 防止元素换行 */
-  padding: 0 rpx(20); /* 左右内边距 */
   scroll-behavior: smooth; /* 平滑滚动效果 */
   -webkit-overflow-scrolling: touch; /* 触摸设备支持 */
   position: relative;
+  flex: 1;
 }
 
 .middle-inner-box{
-  width: rpx(200); /* 设置固定宽度 */
+  width: rpx(130); /* 设置固定宽度 */
   height: 100%; /* 设置固定高度 */
   position: relative;
-  cursor: grab; /* 显示可抓取图标 */
+  cursor: pointer; /* 显示可抓取图标 */
   user-select: none; /* 禁止文本选择 */
   display: inline-block; /* 水平排列 */
-  margin-right: rpx(10); 
+  margin-right: rpx(20);
+  margin-left: rpx(15);
   vertical-align: middle; /* 垂直居中对齐 */
 }
 
@@ -367,17 +440,6 @@ const disableBlockly = (blocklyId = categoryId.value) => {
   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%; 
@@ -434,6 +496,19 @@ const disableBlockly = (blocklyId = categoryId.value) => {
   background-size: 105%; 
   background-position: center;
   background-repeat: no-repeat;
+  position: relative;
+}
+
+/* star图标样式 */
+.star-icon {
+  position: absolute;
+  top: rpx(10);
+  width: rpx(20);
+  height: rpx(20);
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+  z-index: 1;
 }
 
 /* 文字容器 */
@@ -461,10 +536,9 @@ const disableBlockly = (blocklyId = categoryId.value) => {
   background-size: contain;
   background-position: center;
   background-repeat: no-repeat;
-  // bottom: -rpx(20);
-  left: 50%;
+  left: 51%;
   transform: translateX(-50%);
-  top: rpx(149);
+  top: rpx(148);
   z-index: 5;
 }
 .bottom-box {
@@ -475,7 +549,7 @@ const disableBlockly = (blocklyId = categoryId.value) => {
 }
 
 .line-container {
-  width: 80%;
+  width: 65%;
   position: relative;
   display: flex;
   align-items: center;
@@ -491,8 +565,8 @@ const disableBlockly = (blocklyId = categoryId.value) => {
 }
 
 .circle-button {
-  width: rpx(10);
-  height: rpx(10);
+  width: rpx(7);
+  height: rpx(7);
   border-radius: 50%;
   background-color: rgba(255, 255, 255);
   border: rpx(1) solid #00c1fc;
@@ -515,4 +589,54 @@ const disableBlockly = (blocklyId = categoryId.value) => {
   box-shadow: 0 0 rpx(10) rgba(0, 0, 0, 0.5);
 }
 
+/* 轮播图按钮样式 */
+.carousel-btn {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: rpx(30);
+  height: rpx(30);
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  z-index: 10;
+  box-shadow: 0 rpx(5) rpx(10) rgba(0, 0, 0, 0.2);
+  transition: all 0.3s ease;
+}
+
+.carousel-btn:hover:not(.disabled-btn) {
+  background-color: rgba(255, 255, 255, 1);
+  transform: translateY(-50%) scale(1.1);
+}
+
+.carousel-btn:disabled,
+.disabled-btn {
+  opacity: 0.5;
+  cursor: not-allowed;
+  background-color: rgba(200, 200, 200, 0.7);
+  box-shadow: none;
+}
+
+.disabled-icon {
+  color: #ccc !important;
+  filter: grayscale(100%);
+  opacity: 0.6;
+}
+
+.prev-btn {
+  left: 2%;
+}
+
+.next-btn {
+  right: 2%;
+}
+
+.btn-icon {
+  font-size: rpx(15);
+  color: #333;
+}
+
 </style>