index.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <template>
  2. <div class="history">
  3. <i class="history__close" @click="$router.back" />
  4. <div class="history-tabs">
  5. <div
  6. v-for="(tab, index) in TABS"
  7. :key="tab"
  8. :class="[tab, { active: tabIndex === index }]"
  9. @click="onTabClick(index)"
  10. />
  11. </div>
  12. <div class="history-main" ref="mainRef">
  13. <div class="audio-panel" ref="audioPanel">
  14. <AudioPanel />
  15. </div>
  16. <div class="history-panel" ref="videoPanel">
  17. <VideoPanel />
  18. </div>
  19. <div class="history-panel" ref="imagePanel">
  20. <ImagePanel />
  21. </div>
  22. <div class="history-panel" ref="docPanel">
  23. <DocPanel />
  24. </div>
  25. </div>
  26. </div>
  27. </template>
  28. <script setup>
  29. import { ref, onMounted, onUnmounted, nextTick } from "vue";
  30. import AudioPanel from "./components/Audio.vue";
  31. import VideoPanel from "./components/Video.vue";
  32. import ImagePanel from "./components/Image.vue";
  33. import DocPanel from "./components/Doc.vue";
  34. const TABS = ["audio", "video", "image", "doc"];
  35. const tabIndex = ref(0);
  36. const mainRef = ref(null);
  37. const audioPanel = ref(null);
  38. const videoPanel = ref(null);
  39. const imagePanel = ref(null);
  40. const docPanel = ref(null);
  41. const panelEls = () => [
  42. audioPanel.value,
  43. videoPanel.value,
  44. imagePanel.value,
  45. docPanel.value,
  46. ];
  47. function scrollToPanel(index) {
  48. const el = panelEls()[index];
  49. if (!el) return;
  50. // 对 document/viewport 场景,scrollIntoView 直接可用
  51. el.scrollIntoView({ behavior: "smooth", block: "start" });
  52. }
  53. function onTabClick(index) {
  54. tabIndex.value = index;
  55. scrollToPanel(index);
  56. }
  57. let observer = null;
  58. // 根据 viewport(document)滚动计算可见比例并设置高亮
  59. function updateActiveByScroll() {
  60. let bestIdx = -1;
  61. let bestRatio = 0;
  62. const vh = window.innerHeight || document.documentElement.clientHeight;
  63. panelEls().forEach((el, idx) => {
  64. if (!el) return;
  65. const rect = el.getBoundingClientRect();
  66. const elHeight = rect.height || 0;
  67. const visible = Math.max(
  68. 0,
  69. Math.min(rect.bottom, vh) - Math.max(rect.top, 0)
  70. );
  71. const ratio = elHeight > 0 ? visible / elHeight : 0;
  72. if (ratio > bestRatio) {
  73. bestRatio = ratio;
  74. bestIdx = idx;
  75. }
  76. });
  77. if (bestIdx >= 0) tabIndex.value = bestIdx;
  78. }
  79. let scrollHandler = null;
  80. let resizeHandler = null;
  81. onMounted(async () => {
  82. await nextTick();
  83. const roots = panelEls().filter(Boolean);
  84. if (!roots.length) return;
  85. // 使用 viewport 作为 root(document 的滚动)
  86. observer = new IntersectionObserver(
  87. (entries) => {
  88. let best = null;
  89. entries.forEach((e) => {
  90. if (e.isIntersecting) {
  91. if (!best || e.intersectionRatio > best.intersectionRatio) best = e;
  92. }
  93. });
  94. if (best) {
  95. const idx = panelEls().indexOf(best.target);
  96. if (idx >= 0) tabIndex.value = idx;
  97. }
  98. },
  99. {
  100. root: null, // viewport
  101. threshold: [0.25, 0.5, 0.75],
  102. }
  103. );
  104. panelEls().forEach((el) => {
  105. if (el) observer.observe(el);
  106. });
  107. // 绑定到 window 的滚动(document)和 resize
  108. scrollHandler = () => {
  109. updateActiveByScroll();
  110. };
  111. resizeHandler = () => {
  112. updateActiveByScroll();
  113. };
  114. window.addEventListener("scroll", scrollHandler, { passive: true });
  115. window.addEventListener("resize", resizeHandler, { passive: true });
  116. // 初始运行一次以设置正确状态
  117. updateActiveByScroll();
  118. });
  119. onUnmounted(() => {
  120. if (observer) observer.disconnect();
  121. if (scrollHandler) window.removeEventListener("scroll", scrollHandler);
  122. if (resizeHandler) window.removeEventListener("resize", resizeHandler);
  123. });
  124. </script>
  125. <style lang="scss" scoped>
  126. @use "./index.scss";
  127. </style>