refactor(StudyDetailPage): 重构页面布局以优化用户体验
将视频播放和标签页区域固定,滚动区域独立,提升页面交互流畅性。添加标签页切换时的平滑滚动效果,优化用户浏览体验。
Showing
1 changed file
with
76 additions
and
43 deletions
| ... | @@ -4,54 +4,64 @@ | ... | @@ -4,54 +4,64 @@ |
| 4 | --> | 4 | --> |
| 5 | <template> | 5 | <template> |
| 6 | <div class="study-detail-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20"> | 6 | <div class="study-detail-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen pb-20"> |
| 7 | - <div v-if="course" class="flex flex-col"> | 7 | + <div v-if="course" class="flex flex-col h-screen"> |
| 8 | - <!-- 视频播放区域 --> | 8 | + <!-- 固定区域:视频播放和标签页 --> |
| 9 | - <div class="w-full bg-black relative"> | 9 | + <div class="fixed top-0 left-0 right-0 z-10 top-wrapper"> |
| 10 | - <!-- 视频封面和播放按钮 --> | 10 | + <!-- 视频播放区域 --> |
| 11 | - <div v-if="!isPlaying" class="relative w-full" style="aspect-ratio: 16/9;"> | 11 | + <div class="w-full bg-black relative"> |
| 12 | - <img :src="course.cover" :alt="course.title" class="w-full h-full object-cover" /> | 12 | + <!-- 视频封面和播放按钮 --> |
| 13 | - <div class="absolute inset-0 flex items-center justify-center cursor-pointer" @click="startPlay"> | 13 | + <div v-if="!isPlaying" class="relative w-full" style="aspect-ratio: 16/9;"> |
| 14 | - <div | 14 | + <img :src="course.cover" :alt="course.title" class="w-full h-full object-cover" /> |
| 15 | - class="w-24 h-24 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> | 15 | + <div class="absolute inset-0 flex items-center justify-center cursor-pointer" |
| 16 | - <font-awesome-icon icon="circle-play" class="text-5xl text-white" style="font-size: 3rem;" /> | 16 | + @click="startPlay"> |
| 17 | + <div | ||
| 18 | + class="w-24 h-24 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors"> | ||
| 19 | + <font-awesome-icon icon="circle-play" class="text-5xl text-white" | ||
| 20 | + style="font-size: 3rem;" /> | ||
| 21 | + </div> | ||
| 17 | </div> | 22 | </div> |
| 18 | </div> | 23 | </div> |
| 24 | + <!-- 视频播放器 --> | ||
| 25 | + <VideoPlayer v-show="isPlaying" ref="videoPlayerRef" :video-url="course.videoUrl" :autoplay="false" | ||
| 26 | + @onPlay="handleVideoPlay" @onPause="handleVideoPause" /> | ||
| 27 | + </div> | ||
| 28 | + <!-- 标签页区域 --> | ||
| 29 | + <div class="px-4 py-3 bg-white"> | ||
| 30 | + <van-tabs v-model:active="activeTab" sticky animated swipeable shrink @change="handleTabChange"> | ||
| 31 | + <van-tab title="介绍" name="intro"> | ||
| 32 | + </van-tab> | ||
| 33 | + <van-tab :title-style="{ 'min-width': '50%' }" name="comments"> | ||
| 34 | + <template #title>评论(999)</template> | ||
| 35 | + </van-tab> | ||
| 36 | + </van-tabs> | ||
| 19 | </div> | 37 | </div> |
| 20 | - <!-- 视频播放器 --> | ||
| 21 | - <VideoPlayer v-show="isPlaying" ref="videoPlayerRef" :video-url="course.videoUrl" :autoplay="false" | ||
| 22 | - @onPlay="handleVideoPlay" @onPause="handleVideoPause" /> | ||
| 23 | </div> | 38 | </div> |
| 24 | 39 | ||
| 25 | - <!-- 标签页区域 --> | 40 | + <!-- 滚动区域:介绍和评论内容 --> |
| 26 | - <div class="px-4 py-3"> | 41 | + <div class="overflow-y-auto flex-1" :style="{paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')'}"> |
| 27 | - <van-tabs v-model:active="activeTab" sticky animated swipeable> | 42 | + <div id="intro" class="py-4 px-4"> |
| 28 | - <!-- 介绍标签页 --> | 43 | + <h1 class="text-lg font-bold mb-2">{{ course.title }}</h1> |
| 29 | - <van-tab title="介绍" name="intro"> | 44 | + <div class="text-gray-500 text-sm flex items-center gap-2"> |
| 30 | - <div class="py-4"> | 45 | + <span>{{ course.date }}</span> |
| 31 | - <h1 class="text-xl font-bold mb-2">{{ course.title }}</h1> | 46 | + <span class="text-gray-300">|</span> |
| 32 | - <div class="text-gray-500 text-sm"> | 47 | + <span>{{ course.studyCount || 0 }}次学习</span> |
| 33 | - {{ course.studyCount || 0 }}次学习 | 48 | + </div> |
| 34 | - </div> | 49 | + </div> |
| 35 | - </div> | 50 | + |
| 36 | - </van-tab> | 51 | + <div class="h-2 bg-gray-100"></div> |
| 37 | - | 52 | + |
| 38 | - <!-- 评论标签页 --> | 53 | + <div id="comment" class="py-4 px-4 space-y-4"> |
| 39 | - <van-tab title="评论" :title-style="{ 'min-width': '50%' }" name="comments"> | 54 | + <div v-for="comment in comments" :key="comment.id" class="bg-white rounded-lg p-4 shadow-sm"> |
| 40 | - <div class="py-4 space-y-4"> | 55 | + <div class="flex items-center mb-2"> |
| 41 | - <div v-for="comment in comments" :key="comment.id" | 56 | + <img :src="comment.avatar" class="w-8 h-8 rounded-full mr-2" /> |
| 42 | - class="bg-white rounded-lg p-4 shadow-sm"> | 57 | + <div> |
| 43 | - <div class="flex items-center mb-2"> | 58 | + <div class="font-medium">{{ comment.username }}</div> |
| 44 | - <img :src="comment.avatar" class="w-8 h-8 rounded-full mr-2" /> | 59 | + <div class="text-gray-500 text-xs">{{ comment.time }}</div> |
| 45 | - <div> | ||
| 46 | - <div class="font-medium">{{ comment.username }}</div> | ||
| 47 | - <div class="text-gray-500 text-xs">{{ comment.time }}</div> | ||
| 48 | - </div> | ||
| 49 | - </div> | ||
| 50 | - <div class="text-gray-700">{{ comment.content }}</div> | ||
| 51 | </div> | 60 | </div> |
| 52 | </div> | 61 | </div> |
| 53 | - </van-tab> | 62 | + <div class="text-gray-700">{{ comment.content }}</div> |
| 54 | - </van-tabs> | 63 | + </div> |
| 64 | + </div> | ||
| 55 | </div> | 65 | </div> |
| 56 | 66 | ||
| 57 | <!-- 底部操作栏 --> | 67 | <!-- 底部操作栏 --> |
| ... | @@ -126,9 +136,15 @@ const comments = ref([ | ... | @@ -126,9 +136,15 @@ const comments = ref([ |
| 126 | // 设置页面标题 | 136 | // 设置页面标题 |
| 127 | useTitle('学习详情'); | 137 | useTitle('学习详情'); |
| 128 | 138 | ||
| 129 | -// 获取课程详情 | 139 | +const topWrapperHeight = ref(0); |
| 130 | 140 | ||
| 131 | onMounted(() => { | 141 | onMounted(() => { |
| 142 | + nextTick(() => { | ||
| 143 | + const topWrapper = document.querySelector('.top-wrapper'); | ||
| 144 | + if (topWrapper) { | ||
| 145 | + topWrapperHeight.value = topWrapper.clientHeight + 'px'; | ||
| 146 | + } | ||
| 147 | + }) | ||
| 132 | const courseId = route.params.id; | 148 | const courseId = route.params.id; |
| 133 | if (courseId) { | 149 | if (courseId) { |
| 134 | // TODO: 这里需要替换为实际的API调用 | 150 | // TODO: 这里需要替换为实际的API调用 |
| ... | @@ -141,7 +157,8 @@ onMounted(() => { | ... | @@ -141,7 +157,8 @@ onMounted(() => { |
| 141 | studyTime: 3600, | 157 | studyTime: 3600, |
| 142 | type: '视频课程', | 158 | type: '视频课程', |
| 143 | studyCount: 1896, | 159 | studyCount: 1896, |
| 144 | - cover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg' | 160 | + cover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg', |
| 161 | + date: '2024-12-04' | ||
| 145 | }; | 162 | }; |
| 146 | } | 163 | } |
| 147 | }) | 164 | }) |
| ... | @@ -160,4 +177,20 @@ const submitComment = () => { | ... | @@ -160,4 +177,20 @@ const submitComment = () => { |
| 160 | 177 | ||
| 161 | newComment.value = ''; | 178 | newComment.value = ''; |
| 162 | }; | 179 | }; |
| 180 | + | ||
| 181 | +// 处理标签页切换 | ||
| 182 | +const handleTabChange = (name) => { | ||
| 183 | + nextTick(() => { | ||
| 184 | + const element = document.getElementById(name === 'intro' ? 'intro' : 'comment'); | ||
| 185 | + if (element && container) { | ||
| 186 | + const topOffset = element.offsetTop - parseInt(topWrapperHeight.value); | ||
| 187 | + window.scrollTo({ | ||
| 188 | + top: topOffset, | ||
| 189 | + behavior: 'smooth' | ||
| 190 | + }); | ||
| 191 | + } | ||
| 192 | + }) | ||
| 193 | +}; | ||
| 194 | + | ||
| 195 | + | ||
| 163 | </script> | 196 | </script> | ... | ... |
-
Please register or login to post a comment