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