refactor(StudyCoursePage): 重构课程页面布局和滚动逻辑
简化页面结构,移除不必要的嵌套div 使用窗口滚动替代容器内滚动 优化标签页固定和滚动联动逻辑
Showing
1 changed file
with
141 additions
and
186 deletions
| ... | @@ -5,117 +5,98 @@ | ... | @@ -5,117 +5,98 @@ |
| 5 | <template> | 5 | <template> |
| 6 | <AppLayout :has-title="false"> | 6 | <AppLayout :has-title="false"> |
| 7 | <div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen"> | 7 | <div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen"> |
| 8 | - <div v-if="course" class="flex flex-col h-screen"> | 8 | + <div v-if="course" class="bg-white"> |
| 9 | - <!-- 固定区域:课程封面和标签页 --> | 9 | + <van-image class="w-full aspect-video object-cover" :src="course?.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" :alt="course?.title" /> |
| 10 | - <div class=" bg-white"> | 10 | + <div class="p-4"> |
| 11 | - <!-- 课程封面区域 --> | 11 | + <div class="flex items-start justify-between gap-3 mb-2"> |
| 12 | - <van-image class="w-full aspect-video object-cover" :src="course?.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" :alt="course?.title" /> | 12 | + <h1 class="text-black text-xl font-bold flex-1 min-w-0">{{ course?.title }}</h1> |
| 13 | - <div class="p-4"> | 13 | + <div v-if="course?.price !== '0.00'" class="text-orange-500 font-bold text-xl flex-shrink-0">¥{{ course?.price || '0' }}</div> |
| 14 | - <!-- 标题和价格区域 --> | 14 | + <div v-else class="text-orange-500 text-base font-semibold"> 免费 </div> |
| 15 | - <div class="flex items-start justify-between gap-3 mb-2"> | 15 | + </div> |
| 16 | - <h1 class="text-black text-xl font-bold flex-1 min-w-0">{{ course?.title }}</h1> | 16 | + <div class="flex items-center text-gray-500 text-sm"> |
| 17 | - <div v-if="course?.price !== '0.00'" class="text-orange-500 font-bold text-xl flex-shrink-0">¥{{ course?.price || '0' }}</div> | 17 | + <span>已更新 {{ course?.count }} 期</span> |
| 18 | - <div v-else class="text-orange-500 text-base font-semibold"> 免费 </div> | 18 | + <span class="mx-2">|</span> |
| 19 | - </div> | 19 | + <span>{{ course?.buy_count }} 人订阅</span> |
| 20 | - <div class="flex items-center text-gray-500 text-sm"> | ||
| 21 | - <span>已更新 {{ course?.count }} 期</span> | ||
| 22 | - <span class="mx-2">|</span> | ||
| 23 | - <span>{{ course?.buy_count }} 人订阅</span> | ||
| 24 | - </div> | ||
| 25 | </div> | 20 | </div> |
| 21 | + </div> | ||
| 26 | 22 | ||
| 27 | - <div class="h-2 bg-gray-100"></div> | 23 | + <div class="h-2 bg-gray-100"></div> |
| 28 | - | 24 | + |
| 29 | - <!-- 标签页区域 --> | 25 | + <div ref="tabs_wrapper_ref" class="py-3 bg-white sticky top-0 z-10"> |
| 30 | - <div class="py-3 bg-white transition-all duration-300" :class="{'fixed top-0 left-0 right-0 z-10': isTabFixed}" :style="isTabFixed ? { transform: `translateY(0)` } : {}"> | 26 | + <div class="flex justify-around items-center relative" ref="tabs_container_ref"> |
| 31 | - <div class="flex justify-around items-center relative" ref="tabs_container_ref"> | 27 | + <div |
| 32 | - <!-- 动态标签:详情、目录,任务存在时显示打卡互动 --> | 28 | + v-for="(tab, index) in tabs" |
| 33 | - <div | 29 | + :key="tab.name" |
| 34 | - v-for="(tab, index) in tabs" | 30 | + :class="['px-4 py-2 cursor-pointer transition-colors relative', activeTab === tab.name ? 'text-green-600 font-medium' : 'text-gray-600']" |
| 35 | - :key="tab.name" | 31 | + @click="handleTabChange(tab.name)" |
| 36 | - :class="['px-4 py-2 cursor-pointer transition-colors relative', activeTab === tab.name ? 'text-green-600 font-medium' : 'text-gray-600']" | 32 | + :ref="el => setTabRef(tab.name, el)" |
| 37 | - @click="handleTabChange(tab.name)" | 33 | + > |
| 38 | - :ref="el => setTabRef(tab.name, el)" | 34 | + {{ tab.title }} |
| 39 | - > | ||
| 40 | - {{ tab.title }} | ||
| 41 | - </div> | ||
| 42 | - <div | ||
| 43 | - class="absolute bottom-0 left-0 bg-green-600 transition-all duration-300 z-20" | ||
| 44 | - :style="indicatorStyle" | ||
| 45 | - ></div> | ||
| 46 | </div> | 35 | </div> |
| 36 | + <div | ||
| 37 | + class="absolute bottom-0 left-0 bg-green-600 transition-all duration-300 z-20" | ||
| 38 | + :style="indicatorStyle" | ||
| 39 | + ></div> | ||
| 47 | </div> | 40 | </div> |
| 48 | </div> | 41 | </div> |
| 49 | 42 | ||
| 50 | - <!-- 滚动区域:详情、目录和互动内容 --> | 43 | + <div id="detail" class="py-4 px-4"> |
| 51 | - <div ref="scroll_container_ref" class="overflow-y-auto flex-1" | 44 | + <div v-if="course?.feature"> |
| 52 | - :style="{ paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')' }"> | 45 | + <div class="text-black text-xl font-bold mb-2">课程特色</div> |
| 53 | - <!-- 详情区域 --> | 46 | + <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.feature"></div> |
| 54 | - <div id="detail" class="py-4 px-4"> | 47 | + <br /> |
| 55 | - <div v-if="course?.feature"> | 48 | + </div> |
| 56 | - <div class="text-black text-xl font-bold mb-2">课程特色</div> | 49 | + <div v-if="course?.highlights"> |
| 57 | - <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.feature"></div> | 50 | + <div class="text-black text-xl font-bold mb-2">课程亮点</div> |
| 58 | - <br /> | 51 | + <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.highlights"></div> |
| 59 | - </div> | 52 | + <br /> |
| 60 | - <div v-if="course?.highlights"> | ||
| 61 | - <div class="text-black text-xl font-bold mb-2">课程亮点</div> | ||
| 62 | - <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.highlights"></div> | ||
| 63 | - <br /> | ||
| 64 | - </div> | ||
| 65 | - <div v-if="course?.learning_goal"> | ||
| 66 | - <div class="text-black text-xl font-bold mb-2">学习目标</div> | ||
| 67 | - <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.learning_goal"></div> | ||
| 68 | - <br /> | ||
| 69 | - </div> | ||
| 70 | - <van-empty v-if="!course?.feature && !course?.highlights && course?.learning_goal" description="暂无详情" /> | ||
| 71 | </div> | 53 | </div> |
| 54 | + <div v-if="course?.learning_goal"> | ||
| 55 | + <div class="text-black text-xl font-bold mb-2">学习目标</div> | ||
| 56 | + <div class="text-gray-700 text-sm leading-relaxed" v-html="course?.learning_goal"></div> | ||
| 57 | + <br /> | ||
| 58 | + </div> | ||
| 59 | + <van-empty v-if="!course?.feature && !course?.highlights && course?.learning_goal" description="暂无详情" /> | ||
| 60 | + </div> | ||
| 72 | 61 | ||
| 73 | - <div class="h-2 bg-gray-100"></div> | 62 | + <div class="h-2 bg-gray-100"></div> |
| 74 | - | 63 | + |
| 75 | - <!-- 目录区域 --> | 64 | + <div id="catalog" class="py-4"> |
| 76 | - <div id="catalog" class="py-4"> | 65 | + <div v-if="course_lessons.length" class="space-y-4"> |
| 77 | - <div v-if="course_lessons.length" class="space-y-4"> | 66 | + <div v-for="(lesson, index) in course_lessons" :key="index" |
| 78 | - <div v-for="(lesson, index) in course_lessons" :key="index" | 67 | + @click="goToStudyDetail(lesson.id)" |
| 79 | - @click="goToStudyDetail(lesson.id)" | 68 | + class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative"> |
| 80 | - class="bg-white p-4 cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-200 relative"> | 69 | + <div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div> |
| 81 | - <!-- <div v-if="lesson.progress > 0 && lesson.progress < 100" | 70 | + <div class="flex items-center text-sm text-gray-500"> |
| 82 | - class="absolute top-2 right-2 px-2 py-1 bg-green-100 text-green-600 text-xs rounded"> | 71 | + <span>{{ course_type_maps[lesson.course_type] }}</span> |
| 83 | - 没有字段=>上次看到</div> --> | 72 | + <span v-if="course_type_maps[lesson.course_type]" class="mx-2">|</span> |
| 84 | - <div class="text-black text-base font-medium mb-2">{{ lesson.title }}</div> | 73 | + <span>开课时间: {{ lesson.schedule_time ? dayjs(lesson.schedule_time).format('YYYY-MM-DD') : '暂无' }}</span> |
| 85 | - <div class="flex items-center text-sm text-gray-500"> | 74 | + <span class="mx-2">|</span> |
| 86 | - <span>{{ course_type_maps[lesson.course_type] }}</span> | 75 | + <span v-if="lesson.duration">建议时长: {{ lesson.duration }} 分钟</span> |
| 87 | - <span v-if="course_type_maps[lesson.course_type]" class="mx-2">|</span> | ||
| 88 | - <span>开课时间: {{ lesson.schedule_time ? dayjs(lesson.schedule_time).format('YYYY-MM-DD') : '暂无' }}</span> | ||
| 89 | - <span class="mx-2">|</span> | ||
| 90 | - <span v-if="lesson.duration">建议时长: {{ lesson.duration }} 分钟</span> | ||
| 91 | - <!-- <span class="mx-2">|</span> --> | ||
| 92 | - <!-- <span>没有字段=> 已学习{{ lesson?.progress }}%</span> --> | ||
| 93 | - </div> | ||
| 94 | </div> | 76 | </div> |
| 95 | </div> | 77 | </div> |
| 96 | - <van-empty v-else description="暂无目录" /> | ||
| 97 | </div> | 78 | </div> |
| 79 | + <van-empty v-else description="暂无目录" /> | ||
| 80 | + </div> | ||
| 81 | + | ||
| 82 | + <div class="h-2 bg-gray-100"></div> | ||
| 98 | 83 | ||
| 99 | - <div class="h-2 bg-gray-100"></div> | 84 | + <div id="interaction" class="py-4 px-4" v-if="task_list.length > 0"> |
| 100 | - | 85 | + <div class="bg-white rounded-lg p-4 mb-4 cursor-pointer"> |
| 101 | - <!-- 互动区域 --> | 86 | + <div class="flex items-center justify-between" @click="goToCheckin()"> |
| 102 | - <div id="interaction" class="py-4 px-4" v-if="task_list.length > 0"> | 87 | + <div class="flex items-center gap-3"> |
| 103 | - <div class="bg-white rounded-lg p-4 mb-4 cursor-pointer"> | 88 | + <van-icon size="3rem" name="calendar-o" class="text-xl text-gray-600" /> |
| 104 | - <div class="flex items-center justify-between" @click="goToCheckin()"> | 89 | + <div> |
| 105 | - <div class="flex items-center gap-3"> | 90 | + <div class="text-base font-medium">打卡</div> |
| 106 | - <van-icon size="3rem" name="calendar-o" class="text-xl text-gray-600" /> | 91 | + <div class="text-sm text-gray-500">关联{{ task_list.length }}个打卡</div> |
| 107 | - <div> | ||
| 108 | - <div class="text-base font-medium">打卡</div> | ||
| 109 | - <div class="text-sm text-gray-500">关联{{ task_list.length }}个打卡</div> | ||
| 110 | - </div> | ||
| 111 | </div> | 92 | </div> |
| 112 | - <van-icon name="arrow" class="text-gray-400" /> | ||
| 113 | </div> | 93 | </div> |
| 94 | + <van-icon name="arrow" class="text-gray-400" /> | ||
| 114 | </div> | 95 | </div> |
| 115 | </div> | 96 | </div> |
| 116 | - <!-- 添加底部填充区域 --> | ||
| 117 | - <div style="height: 30vh;"></div> | ||
| 118 | </div> | 97 | </div> |
| 98 | + | ||
| 99 | + <div style="height: 30vh;"></div> | ||
| 119 | </div> | 100 | </div> |
| 120 | 101 | ||
| 121 | <!-- 打卡弹窗:统一使用 CheckInDialog 组件 --> | 102 | <!-- 打卡弹窗:统一使用 CheckInDialog 组件 --> |
| ... | @@ -148,8 +129,6 @@ useTitle('课程详情'); | ... | @@ -148,8 +129,6 @@ useTitle('课程详情'); |
| 148 | 129 | ||
| 149 | // 当前激活的标签页 | 130 | // 当前激活的标签页 |
| 150 | const activeTab = ref('detail'); | 131 | const activeTab = ref('detail'); |
| 151 | -const topWrapperHeight = ref(0); | ||
| 152 | -const resizeObserver = ref(null); | ||
| 153 | // tabs DOM映射 | 132 | // tabs DOM映射 |
| 154 | const tabRefs = ref([]); | 133 | const tabRefs = ref([]); |
| 155 | const tabElMap = ref({}); | 134 | const tabElMap = ref({}); |
| ... | @@ -195,8 +174,9 @@ const currentTabIndex = computed(() => { | ... | @@ -195,8 +174,9 @@ const currentTabIndex = computed(() => { |
| 195 | const tabs_container_ref = ref(null); | 174 | const tabs_container_ref = ref(null); |
| 196 | const tabs_container_width = ref(0); | 175 | const tabs_container_width = ref(0); |
| 197 | const tabs_resize_observer = ref(null); | 176 | const tabs_resize_observer = ref(null); |
| 198 | -const scroll_container_ref = ref(null); | 177 | +const tabs_wrapper_ref = ref(null); |
| 199 | -const scroll_container_el = ref(null); | 178 | +const tabs_wrapper_height = ref(0); |
| 179 | +const tabs_wrapper_resize_observer = ref(null); | ||
| 200 | const is_tab_scrolling = ref(false); | 180 | const is_tab_scrolling = ref(false); |
| 201 | 181 | ||
| 202 | /** | 182 | /** |
| ... | @@ -249,24 +229,19 @@ const indicatorStyle = computed(() => { | ... | @@ -249,24 +229,19 @@ const indicatorStyle = computed(() => { |
| 249 | }; | 229 | }; |
| 250 | }); | 230 | }); |
| 251 | 231 | ||
| 252 | -// 计算topWrapperHeight的函数 | 232 | +const updateTabsWrapperHeight = () => { |
| 253 | -const updateTopWrapperHeight = () => { | 233 | + nextTick(() => { |
| 254 | - setTimeout(() => { | 234 | + const el = tabs_wrapper_ref.value; |
| 255 | - nextTick(() => { | 235 | + if (!el) return; |
| 256 | - const topWrapper = document.querySelector('.top-wrapper'); | 236 | + tabs_wrapper_height.value = el.offsetHeight || 0; |
| 257 | - if (topWrapper) { | 237 | + if (tabs_wrapper_resize_observer.value) { |
| 258 | - // 断开之前的observer连接 | 238 | + tabs_wrapper_resize_observer.value.disconnect(); |
| 259 | - if (resizeObserver.value) { | 239 | + } |
| 260 | - resizeObserver.value.disconnect(); | 240 | + tabs_wrapper_resize_observer.value = new ResizeObserver(() => { |
| 261 | - } | 241 | + tabs_wrapper_height.value = el.offsetHeight || 0; |
| 262 | - // 使用 ResizeObserver 监听元素尺寸变化 | ||
| 263 | - resizeObserver.value = new ResizeObserver(() => { | ||
| 264 | - topWrapperHeight.value = `${topWrapper.offsetHeight}px`; | ||
| 265 | - }); | ||
| 266 | - resizeObserver.value.observe(topWrapper); | ||
| 267 | - } | ||
| 268 | }); | 242 | }); |
| 269 | - }, 500) | 243 | + tabs_wrapper_resize_observer.value.observe(el); |
| 244 | + }); | ||
| 270 | }; | 245 | }; |
| 271 | 246 | ||
| 272 | const course = ref([]); | 247 | const course = ref([]); |
| ... | @@ -326,53 +301,31 @@ onMounted(async () => { | ... | @@ -326,53 +301,31 @@ onMounted(async () => { |
| 326 | router.go(-1); | 301 | router.go(-1); |
| 327 | } | 302 | } |
| 328 | /** | 303 | /** |
| 329 | - * 初始化时计算topWrapperHeight | 304 | + * 初始化滚动监听与标签高度 |
| 330 | */ | 305 | */ |
| 331 | - nextTick(() => { | 306 | + window.addEventListener('scroll', handleScroll, { passive: true }); |
| 332 | - if (scroll_container_ref.value) { | 307 | + window.addEventListener('resize', updateTabsWrapperHeight); |
| 333 | - scroll_container_el.value = scroll_container_ref.value; | 308 | + updateTabsWrapperHeight(); |
| 334 | - scroll_container_el.value.addEventListener('scroll', handleScroll, { passive: true }); | ||
| 335 | - } | ||
| 336 | - }); | ||
| 337 | - // 添加窗口大小变化监听 | ||
| 338 | - window.addEventListener('resize', updateTopWrapperHeight); | ||
| 339 | - // 确保在组件挂载后计算高度 | ||
| 340 | - updateTopWrapperHeight(); | ||
| 341 | // 初始化标签容器宽度监听 | 309 | // 初始化标签容器宽度监听 |
| 342 | initTabsContainerWidth(); | 310 | initTabsContainerWidth(); |
| 311 | + handleScroll(); | ||
| 343 | }); | 312 | }); |
| 344 | 313 | ||
| 345 | // 在组件卸载时移除监听器 | 314 | // 在组件卸载时移除监听器 |
| 346 | onUnmounted(() => { | 315 | onUnmounted(() => { |
| 347 | - if (scroll_container_el.value) { | 316 | + window.removeEventListener('scroll', handleScroll); |
| 348 | - scroll_container_el.value.removeEventListener('scroll', handleScroll); | 317 | + window.removeEventListener('resize', updateTabsWrapperHeight); |
| 349 | - scroll_container_el.value = null; | ||
| 350 | - } | ||
| 351 | - window.removeEventListener('resize', updateTopWrapperHeight); | ||
| 352 | - // 组件卸载时取消监听 | ||
| 353 | - if (resizeObserver.value) { | ||
| 354 | - resizeObserver.value.disconnect(); | ||
| 355 | - resizeObserver.value = null; | ||
| 356 | - } | ||
| 357 | // 取消标签容器监听 | 318 | // 取消标签容器监听 |
| 358 | if (tabs_resize_observer.value) { | 319 | if (tabs_resize_observer.value) { |
| 359 | tabs_resize_observer.value.disconnect(); | 320 | tabs_resize_observer.value.disconnect(); |
| 360 | tabs_resize_observer.value = null; | 321 | tabs_resize_observer.value = null; |
| 361 | } | 322 | } |
| 323 | + if (tabs_wrapper_resize_observer.value) { | ||
| 324 | + tabs_wrapper_resize_observer.value.disconnect(); | ||
| 325 | + tabs_wrapper_resize_observer.value = null; | ||
| 326 | + } | ||
| 362 | }); | 327 | }); |
| 363 | 328 | ||
| 364 | -// 处理滚动事件 | ||
| 365 | -// 在script setup中添加 | ||
| 366 | -const isTabFixed = ref(false); | ||
| 367 | - | ||
| 368 | -const get_container_relative_top = (el) => { | ||
| 369 | - const container = scroll_container_ref.value; | ||
| 370 | - if (!container || !el) return 0; | ||
| 371 | - const container_rect = container.getBoundingClientRect(); | ||
| 372 | - const el_rect = el.getBoundingClientRect(); | ||
| 373 | - return el_rect.top - container_rect.top + container.scrollTop; | ||
| 374 | -}; | ||
| 375 | - | ||
| 376 | // 防抖函数 | 329 | // 防抖函数 |
| 377 | const debounce = (fn, delay) => { | 330 | const debounce = (fn, delay) => { |
| 378 | let timer = null; | 331 | let timer = null; |
| ... | @@ -392,37 +345,41 @@ const debounce = (fn, delay) => { | ... | @@ -392,37 +345,41 @@ const debounce = (fn, delay) => { |
| 392 | const handleScroll = debounce(() => { | 345 | const handleScroll = debounce(() => { |
| 393 | if (is_tab_scrolling.value) return; | 346 | if (is_tab_scrolling.value) return; |
| 394 | 347 | ||
| 395 | - const detailElement = document.getElementById('detail'); | 348 | + const buffer = 20; |
| 396 | - const catalogElement = document.getElementById('catalog'); | 349 | + const offset_top = (tabs_wrapper_height.value || 0) + buffer; |
| 397 | - const interactionElement = document.getElementById('interaction'); | 350 | + const current_scroll = window.scrollY + offset_top; |
| 398 | - const container = scroll_container_ref.value; | 351 | + |
| 399 | - if (!detailElement || !catalogElement || !container) return; | 352 | + const sections = tabs.value |
| 400 | - | 353 | + .map((t) => { |
| 401 | - isTabFixed.value = false; | 354 | + const el = document.getElementById(t.name); |
| 402 | - | 355 | + if (!el) return null; |
| 403 | - const scrollTop = container.scrollTop; | 356 | + const rect = el.getBoundingClientRect(); |
| 404 | - const buffer = 20; // 缓冲区域 | 357 | + const top = rect.top + window.scrollY; |
| 405 | - | 358 | + return { name: t.name, top }; |
| 406 | - // 计算各区域顶部位置(互动不存在则设为无穷大) | 359 | + }) |
| 407 | - const detailTop = get_container_relative_top(detailElement) - buffer; | 360 | + .filter(Boolean) |
| 408 | - const catalogTop = get_container_relative_top(catalogElement) - buffer; | 361 | + .sort((a, b) => a.top - b.top); |
| 409 | - const interactionTop = interactionElement ? (get_container_relative_top(interactionElement) - buffer) : Infinity; | 362 | + |
| 410 | - | 363 | + if (!sections.length) return; |
| 411 | - // 页面高度与底部判断 | 364 | + |
| 412 | - const scrollHeight = container.scrollHeight; | 365 | + const doc_height = document.documentElement.scrollHeight; |
| 413 | - const clientHeight = container.clientHeight; | 366 | + const win_height = window.innerHeight; |
| 414 | - const isAtBottom = scrollTop + clientHeight >= scrollHeight - buffer; | 367 | + const is_at_bottom = window.scrollY + win_height >= doc_height - buffer; |
| 415 | - | 368 | + |
| 416 | - // 联动判断 | 369 | + if (is_at_bottom) { |
| 417 | - if (scrollTop <= detailTop) { | 370 | + activeTab.value = sections[sections.length - 1].name; |
| 418 | - activeTab.value = 'detail'; | 371 | + return; |
| 419 | - } else if (interactionElement && (isAtBottom || scrollTop >= interactionTop)) { | ||
| 420 | - activeTab.value = 'interaction'; | ||
| 421 | - } else if (scrollTop >= catalogTop && scrollTop < interactionTop) { | ||
| 422 | - activeTab.value = 'catalog'; | ||
| 423 | - } else if (scrollTop >= detailTop && scrollTop < catalogTop) { | ||
| 424 | - activeTab.value = 'detail'; | ||
| 425 | } | 372 | } |
| 373 | + | ||
| 374 | + let matched = sections[0].name; | ||
| 375 | + for (let i = 0; i < sections.length; i++) { | ||
| 376 | + if (current_scroll >= sections[i].top) { | ||
| 377 | + matched = sections[i].name; | ||
| 378 | + } else { | ||
| 379 | + break; | ||
| 380 | + } | ||
| 381 | + } | ||
| 382 | + activeTab.value = matched; | ||
| 426 | }, 100); | 383 | }, 100); |
| 427 | 384 | ||
| 428 | 385 | ||
| ... | @@ -430,11 +387,6 @@ const handleScroll = debounce(() => { | ... | @@ -430,11 +387,6 @@ const handleScroll = debounce(() => { |
| 430 | // 在script setup中添加 | 387 | // 在script setup中添加 |
| 431 | const previousTab = ref('detail'); | 388 | const previousTab = ref('detail'); |
| 432 | 389 | ||
| 433 | -const getTransitionTiming = (current, previous) => { | ||
| 434 | - const tabOrder = { detail: 0, catalog: 1, interaction: 2 }; | ||
| 435 | - return tabOrder[current] > tabOrder[previous] ? 'cubic-bezier(0.4, 0, 0.2, 1)' : 'cubic-bezier(0.4, 0, 0.2, 1)'; | ||
| 436 | -}; | ||
| 437 | - | ||
| 438 | // 修改handleTabChange函数 | 390 | // 修改handleTabChange函数 |
| 439 | const handleTabChange = (name) => { | 391 | const handleTabChange = (name) => { |
| 440 | previousTab.value = activeTab.value; | 392 | previousTab.value = activeTab.value; |
| ... | @@ -442,9 +394,12 @@ const handleTabChange = (name) => { | ... | @@ -442,9 +394,12 @@ const handleTabChange = (name) => { |
| 442 | is_tab_scrolling.value = true; | 394 | is_tab_scrolling.value = true; |
| 443 | nextTick(() => { | 395 | nextTick(() => { |
| 444 | const element = document.getElementById(name); | 396 | const element = document.getElementById(name); |
| 445 | - if (element && scroll_container_ref.value) { | 397 | + if (element) { |
| 446 | - const topOffset = Math.max(0, get_container_relative_top(element) - 10); | 398 | + const rect = element.getBoundingClientRect(); |
| 447 | - scroll_container_ref.value.scrollTo({ top: topOffset, behavior: 'smooth' }); | 399 | + const buffer = 10; |
| 400 | + const offset_top = (tabs_wrapper_height.value || 0) + buffer; | ||
| 401 | + const topOffset = Math.max(0, rect.top + window.scrollY - offset_top); | ||
| 402 | + window.scrollTo({ top: topOffset, behavior: 'smooth' }); | ||
| 448 | setTimeout(() => { | 403 | setTimeout(() => { |
| 449 | is_tab_scrolling.value = false; | 404 | is_tab_scrolling.value = false; |
| 450 | }, 500); | 405 | }, 500); | ... | ... |
-
Please register or login to post a comment