fix(web): 按真实高度计算虚拟列表窗口

This commit is contained in:
root
2026-07-14 19:04:48 +02:00
parent f879a5ba17
commit a1f7a3bd71
@@ -2,7 +2,14 @@
<!-- Implementation reference: draft-2026-07-13-p0-cloud-console. -->
<script setup lang="ts" generic="T extends object">
import { computed, ref } from "vue";
import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
ref,
watch,
} from "vue";
const props = withDefaults(defineProps<{
items: T[];
@@ -18,8 +25,9 @@ const props = withDefaults(defineProps<{
});
const scrollTop = ref(0);
const viewportHeight = ref(typeof props.height === "number" ? props.height : 420);
const viewportHeight = ref(typeof props.height === "number" ? props.height : 0);
const root = ref<HTMLElement | null>(null);
let resizeObserver: ResizeObserver | null = null;
const startIndex = computed(() => Math.max(0, Math.floor(scrollTop.value / props.itemHeight) - props.overscan));
const endIndex = computed(() => Math.min(props.items.length, Math.ceil((scrollTop.value + viewportHeight.value) / props.itemHeight) + props.overscan));
const visibleItems = computed(() => props.items.slice(startIndex.value, endIndex.value));
@@ -27,6 +35,45 @@ const totalHeight = computed(() => props.items.length * props.itemHeight);
const windowOffset = computed(() => startIndex.value * props.itemHeight);
const rootStyle = computed(() => ({ height: typeof props.height === "number" ? `${props.height}px` : props.height }));
function readViewportHeight(): void {
const height = root.value?.clientHeight ?? 0;
if (height > 0 && height !== viewportHeight.value) viewportHeight.value = height;
}
function stopObservingViewport(): void {
resizeObserver?.disconnect();
resizeObserver = null;
if (typeof window !== "undefined") {
window.removeEventListener("resize", readViewportHeight);
}
}
function observeViewport(): void {
stopObservingViewport();
readViewportHeight();
if (!root.value) return;
if (typeof ResizeObserver !== "undefined") {
resizeObserver = new ResizeObserver(readViewportHeight);
resizeObserver.observe(root.value);
return;
}
if (typeof window !== "undefined") {
window.addEventListener("resize", readViewportHeight, { passive: true });
}
}
onMounted(observeViewport);
onBeforeUnmount(stopObservingViewport);
watch(
() => props.height,
async (height) => {
if (typeof height === "number") viewportHeight.value = height;
await nextTick();
readViewportHeight();
},
);
function onScroll(event: Event): void {
const target = event.currentTarget;
if (!(target instanceof HTMLElement)) return;