hookehuyr

fix(StudyCoursePage): 修复滚动容器引用和滚动事件处理问题

修复滚动容器引用未正确初始化的bug
将全局滚动事件改为容器内滚动事件
优化标签切换时的滚动行为
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
48 </div> 48 </div>
49 49
50 <!-- 滚动区域:详情、目录和互动内容 --> 50 <!-- 滚动区域:详情、目录和互动内容 -->
51 - <div class="overflow-y-auto flex-1" 51 + <div ref="scroll_container_ref" class="overflow-y-auto flex-1"
52 :style="{ paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')' }"> 52 :style="{ paddingTop: topWrapperHeight, height: 'calc(100vh - ' + topWrapperHeight + ')' }">
53 <!-- 详情区域 --> 53 <!-- 详情区域 -->
54 <div id="detail" class="py-4 px-4"> 54 <div id="detail" class="py-4 px-4">
...@@ -195,6 +195,9 @@ const currentTabIndex = computed(() => { ...@@ -195,6 +195,9 @@ const currentTabIndex = computed(() => {
195 const tabs_container_ref = ref(null); 195 const tabs_container_ref = ref(null);
196 const tabs_container_width = ref(0); 196 const tabs_container_width = ref(0);
197 const tabs_resize_observer = ref(null); 197 const tabs_resize_observer = ref(null);
198 +const scroll_container_ref = ref(null);
199 +const scroll_container_el = ref(null);
200 +const is_tab_scrolling = ref(false);
198 201
199 /** 202 /**
200 * 初始化并监听标签容器宽度 203 * 初始化并监听标签容器宽度
...@@ -325,8 +328,12 @@ onMounted(async () => { ...@@ -325,8 +328,12 @@ onMounted(async () => {
325 /** 328 /**
326 * 初始化时计算topWrapperHeight 329 * 初始化时计算topWrapperHeight
327 */ 330 */
328 - // 添加滚动监听 331 + nextTick(() => {
329 - window.addEventListener('scroll', handleScroll); 332 + if (scroll_container_ref.value) {
333 + scroll_container_el.value = scroll_container_ref.value;
334 + scroll_container_el.value.addEventListener('scroll', handleScroll, { passive: true });
335 + }
336 + });
330 // 添加窗口大小变化监听 337 // 添加窗口大小变化监听
331 window.addEventListener('resize', updateTopWrapperHeight); 338 window.addEventListener('resize', updateTopWrapperHeight);
332 // 确保在组件挂载后计算高度 339 // 确保在组件挂载后计算高度
...@@ -337,7 +344,10 @@ onMounted(async () => { ...@@ -337,7 +344,10 @@ onMounted(async () => {
337 344
338 // 在组件卸载时移除监听器 345 // 在组件卸载时移除监听器
339 onUnmounted(() => { 346 onUnmounted(() => {
340 - window.removeEventListener('scroll', handleScroll); 347 + if (scroll_container_el.value) {
348 + scroll_container_el.value.removeEventListener('scroll', handleScroll);
349 + scroll_container_el.value = null;
350 + }
341 window.removeEventListener('resize', updateTopWrapperHeight); 351 window.removeEventListener('resize', updateTopWrapperHeight);
342 // 组件卸载时取消监听 352 // 组件卸载时取消监听
343 if (resizeObserver.value) { 353 if (resizeObserver.value) {
...@@ -354,19 +364,14 @@ onUnmounted(() => { ...@@ -354,19 +364,14 @@ onUnmounted(() => {
354 // 处理滚动事件 364 // 处理滚动事件
355 // 在script setup中添加 365 // 在script setup中添加
356 const isTabFixed = ref(false); 366 const isTabFixed = ref(false);
357 -const tabOriginalTop = ref(0);
358 367
359 -onMounted(async () => { 368 +const get_container_relative_top = (el) => {
360 - setTimeout(() => { 369 + const container = scroll_container_ref.value;
361 - nextTick(() => { 370 + if (!container || !el) return 0;
362 - // 记录标签页原始位置 371 + const container_rect = container.getBoundingClientRect();
363 - const tabElement = document.querySelector('.py-3.bg-white'); 372 + const el_rect = el.getBoundingClientRect();
364 - if (tabElement) { 373 + return el_rect.top - container_rect.top + container.scrollTop;
365 - tabOriginalTop.value = tabElement.getBoundingClientRect().top + window.scrollY; 374 +};
366 - }
367 - })
368 - }, 500);
369 -});
370 375
371 // 防抖函数 376 // 防抖函数
372 const debounce = (fn, delay) => { 377 const debounce = (fn, delay) => {
...@@ -385,27 +390,27 @@ const debounce = (fn, delay) => { ...@@ -385,27 +390,27 @@ const debounce = (fn, delay) => {
385 * 注释:当互动区域不存在时,仅在详情与目录之间联动 390 * 注释:当互动区域不存在时,仅在详情与目录之间联动
386 */ 391 */
387 const handleScroll = debounce(() => { 392 const handleScroll = debounce(() => {
393 + if (is_tab_scrolling.value) return;
394 +
388 const detailElement = document.getElementById('detail'); 395 const detailElement = document.getElementById('detail');
389 const catalogElement = document.getElementById('catalog'); 396 const catalogElement = document.getElementById('catalog');
390 const interactionElement = document.getElementById('interaction'); 397 const interactionElement = document.getElementById('interaction');
391 - const tabElement = document.querySelector('.py-3.bg-white'); 398 + const container = scroll_container_ref.value;
392 - if (!detailElement || !catalogElement || !tabElement) return; 399 + if (!detailElement || !catalogElement || !container) return;
393 400
394 - const currentScrollY = window.scrollY; 401 + isTabFixed.value = false;
395 - isTabFixed.value = currentScrollY >= tabOriginalTop.value;
396 402
397 - const scrollTop = window.scrollY; 403 + const scrollTop = container.scrollTop;
398 - const tabHeight = tabElement.offsetHeight; 404 + const buffer = 20; // 缓冲区域
399 - const buffer = 50; // 缓冲区域
400 405
401 // 计算各区域顶部位置(互动不存在则设为无穷大) 406 // 计算各区域顶部位置(互动不存在则设为无穷大)
402 - const detailTop = detailElement.offsetTop - tabHeight - buffer; 407 + const detailTop = get_container_relative_top(detailElement) - buffer;
403 - const catalogTop = catalogElement.offsetTop - tabHeight - buffer; 408 + const catalogTop = get_container_relative_top(catalogElement) - buffer;
404 - const interactionTop = interactionElement ? (interactionElement.offsetTop - tabHeight - buffer) : Infinity; 409 + const interactionTop = interactionElement ? (get_container_relative_top(interactionElement) - buffer) : Infinity;
405 410
406 // 页面高度与底部判断 411 // 页面高度与底部判断
407 - const scrollHeight = document.documentElement.scrollHeight; 412 + const scrollHeight = container.scrollHeight;
408 - const clientHeight = document.documentElement.clientHeight; 413 + const clientHeight = container.clientHeight;
409 const isAtBottom = scrollTop + clientHeight >= scrollHeight - buffer; 414 const isAtBottom = scrollTop + clientHeight >= scrollHeight - buffer;
410 415
411 // 联动判断 416 // 联动判断
...@@ -434,15 +439,17 @@ const getTransitionTiming = (current, previous) => { ...@@ -434,15 +439,17 @@ const getTransitionTiming = (current, previous) => {
434 const handleTabChange = (name) => { 439 const handleTabChange = (name) => {
435 previousTab.value = activeTab.value; 440 previousTab.value = activeTab.value;
436 activeTab.value = name; // 立即更新activeTab的值 441 activeTab.value = name; // 立即更新activeTab的值
437 - let offset = 100; // 调整偏移量, 配合目录的高度 442 + is_tab_scrolling.value = true;
438 nextTick(() => { 443 nextTick(() => {
439 const element = document.getElementById(name); 444 const element = document.getElementById(name);
440 - if (element) { 445 + if (element && scroll_container_ref.value) {
441 - const topOffset = element.offsetTop - offset - parseInt(topWrapperHeight.value); 446 + const topOffset = Math.max(0, get_container_relative_top(element) - 10);
442 - window.scrollTo({ 447 + scroll_container_ref.value.scrollTo({ top: topOffset, behavior: 'smooth' });
443 - top: topOffset, 448 + setTimeout(() => {
444 - behavior: 'smooth' 449 + is_tab_scrolling.value = false;
445 - }); 450 + }, 500);
451 + } else {
452 + is_tab_scrolling.value = false;
446 } 453 }
447 }); 454 });
448 }; 455 };
......