From e7d64e28428cade0504a2548c6484b2ff50b0c90 Mon Sep 17 00:00:00 2001 From: jingrow Date: Sun, 31 May 2026 03:41:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96PptxRendererPreview.vue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pagetype/file/PptxRendererPreview.vue | 72 +++++++++++++++++-- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/frontend/src/views/pagetype/file/PptxRendererPreview.vue b/frontend/src/views/pagetype/file/PptxRendererPreview.vue index c1641f9ba..971141c5b 100644 --- a/frontend/src/views/pagetype/file/PptxRendererPreview.vue +++ b/frontend/src/views/pagetype/file/PptxRendererPreview.vue @@ -3,13 +3,13 @@
- -
- - {{ currentSlide + 1 }} / {{ totalSlides }} -
@@ -50,6 +50,8 @@ const currentSlide = ref(0) const totalSlides = ref(0) let viewer: PptxViewer | null = null +let resizeObserver: ResizeObserver | null = null +let resizeTimer: ReturnType | null = null function resolveUrl(url: string): string { if (!url) return '' @@ -57,6 +59,44 @@ function resolveUrl(url: string): string { return window.location.origin + url } +/** + * 库 fitMode:'contain' 自动按宽度缩放(宽度变化时自动重渲染), + * 但不考虑高度约束。当高度溢出时,用 setZoom 缩小使宽高都适应。 + * zoom=100 表示库默认(按宽度 fit),<100 表示需进一步缩小以适应高度。 + */ +function calcFitZoom(): number { + if (!viewer || !containerRef.value) return 100 + const cW = containerRef.value.clientWidth + const cH = containerRef.value.clientHeight + const sW = viewer.slideWidth + const sH = viewer.slideHeight + if (!cW || !cH || !sW || !sH) return 100 + + // 库 contain 模式基础 scale = cW / sW(按宽度 fit) + // 检查按此 scale 渲染后高度是否溢出 + const scaleByW = cW / sW + const scaleByH = cH / sH + if (scaleByH < scaleByW) { + // 高度是瓶颈,需缩小到按高度 fit + return Math.round((scaleByH / scaleByW) * 100) + } + return 100 +} + +/** 容器尺寸变化时重算 zoom(库已自动按宽度 fit,只需修正高度溢出) */ +async function handleResize() { + if (!viewer) return + const zoom = calcFitZoom() + if (zoom !== viewer.zoomPercent) { + await viewer.setZoom(zoom) + } +} + +function scheduleResize() { + if (resizeTimer) clearTimeout(resizeTimer) + resizeTimer = setTimeout(() => handleResize(), 200) +} + function goPrev() { if (!viewer || currentSlide.value <= 0) return viewer.goToSlide(currentSlide.value - 1) @@ -111,7 +151,8 @@ async function loadPresentation(fileUrl: string) { if (!containerRef.value) return - // slide 模式:一次渲染一页,配合自定义导航 + // fitMode:'contain' 库自动按宽度 fit(宽度变化时自动重渲染), + // 高度溢出由 calcFitZoom + setZoom 修正 viewer = await PptxViewer.open(buffer, containerRef.value, { renderMode: 'slide', zipLimits: RECOMMENDED_ZIP_LIMITS, @@ -124,6 +165,12 @@ async function loadPresentation(fileUrl: string) { totalSlides.value = viewer.slideCount currentSlide.value = viewer.currentSlideIndex + // 初始修正高度溢出 + const zoom = calcFitZoom() + if (zoom < 100) { + await viewer.setZoom(zoom) + } + loading.value = false emit('loaded') } catch (e) { @@ -139,12 +186,20 @@ watch(() => props.fileUrl, (newUrl) => { }, { immediate: true }) onMounted(() => { - // 自动聚焦以接收键盘事件 const el = containerRef.value?.closest('.pptx-renderer-preview') as HTMLElement el?.focus() + + // 容器尺寸变化时重算 zoom + if (containerRef.value) { + resizeObserver = new ResizeObserver(() => scheduleResize()) + resizeObserver.observe(containerRef.value) + } }) onBeforeUnmount(() => { + if (resizeTimer) clearTimeout(resizeTimer) + resizeObserver?.disconnect() + resizeObserver = null viewer?.destroy() viewer = null }) @@ -168,6 +223,9 @@ onBeforeUnmount(() => { flex: 1; min-height: 0; overflow: hidden; + display: flex; + align-items: center; + justify-content: center; } /* ===== 导航栏 ===== */