StudyCoursePage.vue
9.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
<!--
* @Date: 2024-01-17
* @Description: 课程详情页面
-->
<template>
<div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen">
<div v-if="course" class="flex flex-col h-screen">
<!-- 固定区域:课程封面和标签页 -->
<div class="fixed top-0 left-0 right-0 z-10 top-wrapper bg-white">
<!-- 课程封面区域 -->
<van-image class="w-full aspect-video object-cover" :src="course?.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" :alt="course?.title" />
<div class="p-4">
<h1 class="text-black text-xl font-bold mb-2">{{ course?.title }}</h1>
<div class="flex items-center text-gray-500 text-sm">
<span>已更新 没有字段 期</span>
<span class="mx-2">|</span>
<span>没有字段 人订阅</span>
</div>
</div>
<div class="h-2 bg-gray-100"></div>
<!-- 标签页区域 -->
<div class="py-3 bg-white">
<van-tabs v-model:active="activeTab" sticky animated swipeable shrink @change="handleTabChange">
<van-tab title="详情" name="detail">
</van-tab>
<van-tab title="目录" name="catalog">
</van-tab>
<van-tab title="课程互动" name="interaction">
</van-tab>
</van-tabs>
</div>
</div>
<!-- 滚动区域:详情、目录和互动内容 -->
<div class="overflow-y-auto flex-1"
:style="{ paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')' }">
<!-- 详情区域 -->
<div id="detail" class="py-4 px-4">
<div v-if="course?.feature">
<div class="text-black text-xl font-bold mb-2">课程特色</div>
<div class="text-gray-700 text-sm leading-relaxed" v-html="course?.feature"></div>
<br />
</div>
<div v-if="course?.highlights">
<div class="text-black text-xl font-bold mb-2">课程亮点</div>
<div class="text-gray-700 text-sm leading-relaxed" v-html="course?.highlights"></div>
<br />
</div>
<div v-if="course?.learning_goal">
<div class="text-black text-xl font-bold mb-2">学习目标</div>
<div class="text-gray-700 text-sm leading-relaxed" v-html="course?.learning_goal"></div>
<br />
</div>
<van-empty v-else description="暂无详情" />
</div>
<div class="h-2 bg-gray-100"></div>
<!-- 目录区域 -->
<div id="catalog" class="py-4">
<div v-if="course_lessons.length" class="space-y-4">
<div v-for="(lesson, index) in course_lessons" :key="index"
@click="router.push(`/studyDetail/${lesson.id}`)"
class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative">
<div v-if="lesson.progress > 0 && lesson.progress < 100"
class="absolute top-2 right-2 px-2 py-1 bg-green-100 text-green-600 text-xs rounded">
没有字段上次看到</div>
<div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div>
<div class="flex items-center text-sm text-gray-500">
<span>{{ course_type_maps[lesson.course_type] }}</span>
<span>{{ dayjs(course.schedule_time).format('YYYY-MM-DD') }}</span>
<span class="mx-2">|</span>
<span>没有字段 次学习</span>
<span class="mx-2">|</span>
<span>没有字段 已学习{{ lesson?.progress }}%</span>
</div>
</div>
</div>
<van-empty v-else description="暂无目录" />
</div>
<div class="h-2 bg-gray-100"></div>
<!-- 互动区域 -->
<div id="interaction" class="py-4 px-4">
<div class="bg-white rounded-lg p-4 mb-4 cursor-pointer">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<van-icon size="3rem" name="calendar-o" class="text-xl text-gray-600" />
<div>
<div class="text-base font-medium">打卡</div>
<div class="text-sm text-gray-500">关联7个打卡</div>
</div>
</div>
<van-icon name="arrow" class="text-gray-400" />
</div>
</div>
</div>
<!-- 添加底部填充区域 -->
<div style="height: 30vh;"></div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, onUnmounted } from 'vue';
import { useTitle } from '@vueuse/core';
import { useRouter } from "vue-router";
import dayjs from 'dayjs';
// 导入接口
import { getCourseDetailAPI } from '@/api/course'
const router = useRouter();
// 页面标题
useTitle('课程详情');
// 当前激活的标签页
const activeTab = ref('detail');
const topWrapperHeight = ref(0);
const resizeObserver = ref(null);
// 计算topWrapperHeight的函数
const updateTopWrapperHeight = () => {
nextTick(() => {
const topWrapper = document.querySelector('.top-wrapper');
if (topWrapper) {
// 使用 ResizeObserver 监听元素尺寸变化
resizeObserver.value = new ResizeObserver(() => {
topWrapperHeight.value = `${topWrapper.offsetHeight}px`;
});
resizeObserver.value.observe(topWrapper);
}
});
};
const course = ref([]);
const course_lessons = ref([]);
const course_type_maps = ref({
video: '视频',
audio: '音频',
image: '图片',
file: '文件',
})
onMounted(async () => {
/**
* 组件挂载时获取课程详情
*/
// 获取课程ID
const courseId = router.currentRoute.value.params.id;
// 调用接口获取课程详情
const { code, data } = await getCourseDetailAPI({ i: courseId });
if (code) {
course.value = data;
course_lessons.value = data.schedule || [];
}
/**
* 初始化时计算topWrapperHeight
*/
// 添加滚动监听
window.addEventListener('scroll', handleScroll);
// 添加窗口大小变化监听
window.addEventListener('resize', updateTopWrapperHeight);
// 确保在组件挂载后计算高度
updateTopWrapperHeight();
});
// 在组件卸载时移除监听器
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', updateTopWrapperHeight);
// 组件卸载时取消监听
resizeObserver.value.disconnect();
});
// 处理滚动事件
const handleScroll = () => {
const detailElement = document.getElementById('detail');
const catalogElement = document.getElementById('catalog');
const interactionElement = document.getElementById('interaction');
if (!detailElement || !catalogElement || !interactionElement) return;
const scrollTop = window.scrollY;
const catalogOffset = catalogElement.offsetTop - parseInt(topWrapperHeight.value);
const interactionOffset = interactionElement.offsetTop - parseInt(topWrapperHeight.value);
// 根据滚动位置更新activeTab
if (scrollTop >= interactionOffset) {
activeTab.value = 'interaction';
} else if (scrollTop >= catalogOffset) {
activeTab.value = 'catalog';
} else {
activeTab.value = 'detail';
}
};
// 处理标签页切换
const handleTabChange = (name) => {
nextTick(() => {
const element = document.getElementById(name);
if (element) {
const topOffset = element.offsetTop - parseInt(topWrapperHeight.value);
window.scrollTo({
top: topOffset,
behavior: 'smooth'
});
}
});
};
// 课程数据
// const course = ref({
// title: '开学礼·止的智慧·心法老师·20241001',
// coverImage: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
// updateTime: '2024.01.17',
// viewCount: 1897,
// description: '这是一门关于心法的课程,帮助学员掌握止的智慧...',
// lessons: [
// {
// title: '第一课:止的基础',
// duration: '45分钟',
// progress: 100
// },
// {
// title: '第二课:止的技巧',
// duration: '50分钟',
// progress: 60
// },
// {
// title: '第三课:止的应用',
// duration: '40分钟',
// progress: 0
// }
// ]
// });
</script>
<style scoped>
.study-course-page {
min-height: 100vh;
}
.course-header {
height: auto;
}
.course-tabs {
background-color: #fff;
}
.course-catalog {
background-color: #fff;
}
</style>