|
|
@@ -0,0 +1,139 @@
|
|
|
+<template>
|
|
|
+ <div class="history">
|
|
|
+ <i class="history__close" @click="$router.back" />
|
|
|
+
|
|
|
+ <div class="history-tabs">
|
|
|
+ <div
|
|
|
+ v-for="(tab, index) in TABS"
|
|
|
+ :key="tab"
|
|
|
+ :class="[tab, { active: tabIndex === index }]"
|
|
|
+ @click="onTabClick(index)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="history-main" ref="mainRef">
|
|
|
+ <!-- <AudioPanel /> -->
|
|
|
+ <div class="history-panel" ref="videoPanel">
|
|
|
+ <VideoPanel />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="history-panel" ref="imagePanel">
|
|
|
+ <ImagePanel />
|
|
|
+ </div>
|
|
|
+ <!-- <DocPanel /> -->
|
|
|
+ <div style="height: 100vh" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted, onUnmounted, nextTick } from "vue";
|
|
|
+import AudioPanel from "./components/Audio.vue";
|
|
|
+import VideoPanel from "./components/Video.vue";
|
|
|
+import ImagePanel from "./components/Image.vue";
|
|
|
+import DocPanel from "./components/Doc.vue";
|
|
|
+
|
|
|
+const TABS = ["video", "image"];
|
|
|
+const tabIndex = ref(0);
|
|
|
+
|
|
|
+const mainRef = ref(null);
|
|
|
+const videoPanel = ref(null);
|
|
|
+const imagePanel = ref(null);
|
|
|
+
|
|
|
+const panelEls = () => [videoPanel.value, imagePanel.value];
|
|
|
+
|
|
|
+function scrollToPanel(index) {
|
|
|
+ const el = panelEls()[index];
|
|
|
+ if (!el) return;
|
|
|
+ // 对 document/viewport 场景,scrollIntoView 直接可用
|
|
|
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
|
+}
|
|
|
+
|
|
|
+function onTabClick(index) {
|
|
|
+ tabIndex.value = index;
|
|
|
+ scrollToPanel(index);
|
|
|
+}
|
|
|
+
|
|
|
+let observer = null;
|
|
|
+
|
|
|
+// 根据 viewport(document)滚动计算可见比例并设置高亮
|
|
|
+function updateActiveByScroll() {
|
|
|
+ let bestIdx = -1;
|
|
|
+ let bestRatio = 0;
|
|
|
+ const vh = window.innerHeight || document.documentElement.clientHeight;
|
|
|
+
|
|
|
+ panelEls().forEach((el, idx) => {
|
|
|
+ if (!el) return;
|
|
|
+ const rect = el.getBoundingClientRect();
|
|
|
+ const elHeight = rect.height || 0;
|
|
|
+ const visible = Math.max(
|
|
|
+ 0,
|
|
|
+ Math.min(rect.bottom, vh) - Math.max(rect.top, 0)
|
|
|
+ );
|
|
|
+ const ratio = elHeight > 0 ? visible / elHeight : 0;
|
|
|
+ if (ratio > bestRatio) {
|
|
|
+ bestRatio = ratio;
|
|
|
+ bestIdx = idx;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (bestIdx >= 0) tabIndex.value = bestIdx;
|
|
|
+}
|
|
|
+
|
|
|
+let scrollHandler = null;
|
|
|
+let resizeHandler = null;
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await nextTick();
|
|
|
+ const roots = panelEls().filter(Boolean);
|
|
|
+ if (!roots.length) return;
|
|
|
+
|
|
|
+ // 使用 viewport 作为 root(document 的滚动)
|
|
|
+ observer = new IntersectionObserver(
|
|
|
+ (entries) => {
|
|
|
+ let best = null;
|
|
|
+ entries.forEach((e) => {
|
|
|
+ if (e.isIntersecting) {
|
|
|
+ if (!best || e.intersectionRatio > best.intersectionRatio) best = e;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (best) {
|
|
|
+ const idx = panelEls().indexOf(best.target);
|
|
|
+ if (idx >= 0) tabIndex.value = idx;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ root: null, // viewport
|
|
|
+ threshold: [0.25, 0.5, 0.75],
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ panelEls().forEach((el) => {
|
|
|
+ if (el) observer.observe(el);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 绑定到 window 的滚动(document)和 resize
|
|
|
+ scrollHandler = () => {
|
|
|
+ updateActiveByScroll();
|
|
|
+ };
|
|
|
+ resizeHandler = () => {
|
|
|
+ updateActiveByScroll();
|
|
|
+ };
|
|
|
+
|
|
|
+ window.addEventListener("scroll", scrollHandler, { passive: true });
|
|
|
+ window.addEventListener("resize", resizeHandler, { passive: true });
|
|
|
+
|
|
|
+ // 初始运行一次以设置正确状态
|
|
|
+ updateActiveByScroll();
|
|
|
+});
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ if (observer) observer.disconnect();
|
|
|
+ if (scrollHandler) window.removeEventListener("scroll", scrollHandler);
|
|
|
+ if (resizeHandler) window.removeEventListener("resize", resizeHandler);
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@use "./index.scss";
|
|
|
+</style>
|