基础用法
本库提供两种预览组件:
FilePreviewModal— 全屏弹窗预览,默认通过 Portal/Teleport 挂载到document.bodyFilePreviewEmbed— 嵌入式预览,直接内联到你指定的 div 容器中
两者底层共用一套渲染逻辑,API 风格也非常相似。根据你的场景选择即可,或者两者搭配使用。
基本示例
最简单的使用方式:
tsx
import { useState } from 'react'
import { FilePreviewModal } from '@eternalheart/react-file-preview'
import '@eternalheart/react-file-preview/style.css'
function App() {
const [isOpen, setIsOpen] = useState(false)
const [currentIndex, setCurrentIndex] = useState(0)
const files = [
{ url: 'https://example.com/image.jpg', name: 'image.jpg' },
{ url: 'https://example.com/document.pdf', name: 'document.pdf' }
]
return (
<>
<button onClick={() => setIsOpen(true)}>预览文件</button>
<FilePreviewModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
files={files}
currentIndex={currentIndex}
onNavigate={setCurrentIndex}
/>
</>
)
}vue
<script setup>
import { ref } from 'vue'
import { FilePreviewModal } from '@eternalheart/vue-file-preview'
import '@eternalheart/vue-file-preview/style.css'
const isOpen = ref(false)
const currentIndex = ref(0)
const files = [
{ url: 'https://example.com/image.jpg', name: 'image.jpg' },
{ url: 'https://example.com/document.pdf', name: 'document.pdf' }
]
</script>
<template>
<button @click="isOpen = true">预览文件</button>
<FilePreviewModal
:is-open="isOpen"
:files="files"
:current-index="currentIndex"
@close="isOpen = false"
@navigate="(i) => (currentIndex = i)"
/>
</template>文件对象格式
文件对象支持三种格式:
URL 字符串
ts
const files = ['https://example.com/file.pdf']PreviewFileLink 对象
ts
const file = {
name: 'document.pdf',
url: 'https://example.com/file.pdf',
type: 'application/pdf'
}原生 File 对象
ts
const file = new File(['content'], 'document.txt', { type: 'text/plain' })处理文件上传
tsx
function FileUploadExample() {
const [files, setFiles] = useState<PreviewFileInput[]>([])
const [isOpen, setIsOpen] = useState(false)
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = Array.from(e.target.files || [])
setFiles(selectedFiles)
setIsOpen(true)
}
return (
<>
<input type="file" multiple onChange={handleFileChange} />
<FilePreviewModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
files={files}
currentIndex={0}
/>
</>
)
}vue
<script setup lang="ts">
import { ref } from 'vue'
import { FilePreviewModal, type PreviewFileInput } from '@eternalheart/vue-file-preview'
const files = ref<PreviewFileInput[]>([])
const isOpen = ref(false)
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
files.value = Array.from(target.files || [])
isOpen.value = true
}
</script>
<template>
<input type="file" multiple @change="handleFileChange" />
<FilePreviewModal
:is-open="isOpen"
:files="files"
:current-index="0"
@close="isOpen = false"
/>
</template>嵌入式预览 (FilePreviewEmbed)
除了弹窗,你也可以把预览直接内联到页面任意位置,适合详情面板、分栏布局、仪表盘等场景。
tsx
import { useState } from 'react'
import { FilePreviewEmbed } from '@eternalheart/react-file-preview'
import '@eternalheart/react-file-preview/style.css'
function InlinePreview() {
const [index, setIndex] = useState(0)
const files = [
'https://example.com/image.jpg',
{ name: 'doc.pdf', url: '/doc.pdf', type: 'application/pdf' }
]
// 父容器必须有明确的高度,嵌入组件才能撑开
return (
<div style={{ width: '100%', height: 520 }}>
<FilePreviewEmbed
files={files}
currentIndex={index}
onNavigate={setIndex}
/>
</div>
)
}vue
<script setup>
import { ref } from 'vue'
import { FilePreviewEmbed } from '@eternalheart/vue-file-preview'
import '@eternalheart/vue-file-preview/style.css'
const index = ref(0)
const files = [
'https://example.com/image.jpg',
{ name: 'doc.pdf', url: '/doc.pdf', type: 'application/pdf' }
]
</script>
<template>
<div style="width: 100%; height: 520px">
<FilePreviewEmbed
:files="files"
:current-index="index"
@navigate="(i) => (index = i)"
/>
</div>
</template>显式指定尺寸
除了默认填充父容器,你也可以通过 width / height props 显式指定:
tsx
<FilePreviewEmbed files={files} width={800} height={500} />vue
<FilePreviewEmbed :files="files" :width="800" :height="500" />与弹窗模式的主要区别
| 特性 | FilePreviewModal | FilePreviewEmbed |
|---|---|---|
| 渲染方式 | Portal / Teleport 到 document.body | 组件树内联 |
| 遮罩背景 | ✅ 半透明黑色全屏 | ❌ 无 |
isOpen / onClose | ✅ 必填 | ❌ 不存在 |
| 工具栏"关闭"按钮 | ✅ 显示 | ❌ 不显示 |
Esc 键关闭 | ✅ | ❌ |
| ← → 键导航 | 全局监听 | 仅容器 focus 时响应,不影响页面其他交互 |
自定义渲染器
你可以为特定文件类型提供自定义渲染器:
tsx
import type { CustomRenderer } from '@eternalheart/react-file-preview'
const customRenderers: CustomRenderer[] = [
{
test: (file) => file.name.endsWith('.custom'),
render: (file) => (
<div>
<h2>自定义渲染器</h2>
<p>文件名: {file.name}</p>
</div>
)
}
]
<FilePreviewModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
files={files}
currentIndex={0}
customRenderers={customRenderers}
/>vue
<script setup lang="ts">
import { defineComponent, h } from 'vue'
import { FilePreviewModal, type CustomRenderer } from '@eternalheart/vue-file-preview'
// 自定义渲染器组件
const CustomRendererComp = defineComponent({
props: ['file'],
setup(props) {
return () => h('div', [
h('h2', '自定义渲染器'),
h('p', `文件名: ${props.file.name}`)
])
}
})
const customRenderers: CustomRenderer[] = [
{
test: (file) => file.name.endsWith('.custom'),
render: () => CustomRendererComp
}
]
</script>
<template>
<FilePreviewModal
:is-open="isOpen"
:files="files"
:current-index="0"
:custom-renderers="customRenderers"
@close="isOpen = false"
/>
</template>控制导航
tsx
<FilePreviewModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
files={files}
currentIndex={currentIndex}
onNavigate={(index) => {
console.log('切换到文件:', index)
setCurrentIndex(index)
}}
/>vue
<FilePreviewModal
:is-open="isOpen"
:files="files"
:current-index="currentIndex"
@close="isOpen = false"
@navigate="(index) => {
console.log('切换到文件:', index)
currentIndex = index
}"
/>渲染机制
Portal / Teleport 渲染
FilePreviewModal 使用 React Portal(或 Vue Teleport)将模态框渲染到 document.body,这确保了:
最高层级: 模态框始终显示在页面最上层,不受父元素
z-index影响样式隔离: 避免父元素的 CSS 样式(如
overflow: hidden)影响模态框定位准确: 模态框相对于视口定位,不受父元素定位影响
这意味着你可以在任何位置使用 FilePreviewModal,无需担心层级和定位问题。