|
|
@@ -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>
|