安装依赖
html
复制代码
npm install @vue-office/core @vue-office/docx @vue-office/excel @vue-office/pdf
一,封装:
1.Excel预览组件
vue
复制代码
<script lang="ts" setup>
import VueOfficeExcel from '@vue-office/excel'
import '@vue-office/excel/lib/index.css'
const props = defineProps<{
src: string // 文件URL
options?: object // 转换配置
}>()
</script>
<template>
<VueOfficeExcel
:src="props.src"
:options="props.options"
style="height: 100vh;"
/>
</template>
2.Word预览组件
vue
复制代码
<script lang="ts" setup>
import VueOfficeDocx from '@vue-office/docx'
import '@vue-office/docx/lib/index.css'
const props = defineProps<{
src: string
options?: {
ignoreFonts?: boolean // 是否忽略字体样式
}
}>()
</script>
<template>
<VueOfficeDocx
:src="props.src"
:options="props.options"
style="height: 100vh;"
/>
</template>
3.PDF预览组件
vue
复制代码
<script lang="ts" setup>
import VueOfficePdf from '@vue-office/pdf'
const props = defineProps<{
src: string
options?: {
password?: string // 解密密码
disableAutoFetch?: boolean // 禁用自动获取
}
}>()
</template>
<template>
<VueOfficePdf
:src="props.src"
:options="props.options"
style="height: 100vh;"
/>
</template>
通用文件预览器
vue
复制代码
<script lang="ts" setup>
import { ref, watch } from 'vue'
import VueOfficeDocx from '@vue-office/docx'
import VueOfficeExcel from '@vue-office/excel'
import VueOfficePdf from '@vue-office/pdf'
import type { Component } from 'vue'
const props = defineProps<{
src: string
fileType?: 'docx'|'xlsx'|'pdf'
}>()
const component = ref<Component|null>(null)
const loading = ref(false)
watch(() => props.src, async (newVal) => {
if (!newVal) return
loading.value = true
const extension = props.fileType || newVal.split('.').pop()?.toLowerCase()
switch(extension) {
case 'docx':
component.value = VueOfficeDocx
break
case 'xlsx':
component.value = VueOfficeExcel
break
case 'pdf':
component.value = VueOfficePdf
break
default:
console.error('Unsupported file type')
}
loading.value = false
}, { immediate: true })
</script>
<template>
<div v-if="loading">Loading...</div>
<component
v-else
:is="component"
:src="src"
style="height: 100vh;"
/>
</template>
二,不封装
1.拖放区域以及上传按钮
html
复制代码
<div class="upload-section">
<el-upload
ref="uploadRef"
class="upload-demo"
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
:accept="acceptTypes"
drag
action="#"
>
<el-icon class="upload-icon">
<UploadFilled/>
</el-icon>
<div class="el-upload__text">
点击选择或拖放文件到此区域
</div>
<div class="el-upload__tip">
支持 PDF (.pdf), Word (.docx, .doc), Excel (.xlsx, .xls, .csv) 格式
</div>
</el-upload>
<!-- 快速选择按钮 -->
<div class="quick-buttons">
<el-button
class="quick-btn pdf-btn"
@click="triggerFileSelect('pdf')"
>
<el-icon>
<Document/>
</el-icon>
<span>PDF 文档</span>
</el-button>
<el-button
class="quick-btn word-btn"
@click="triggerFileSelect('word')"
>
<el-icon>
<Document/>
</el-icon>
<span>Word 文档</span>
</el-button>
<el-button
class="quick-btn excel-btn"
@click="triggerFileSelect('excel')"
>
<el-icon>
<Document/>
</el-icon>
<span>Excel 表格</span>
</el-button>
</div>
</div>
2.文档预览区域
html
复制代码
<div v-if="selectedFile" class="preview-section">
<div class="preview-header">
<div class="file-info">
<el-icon class="file-icon">
<Document/>
</el-icon>
<span class="file-name">{{ selectedFile.name }}</span>
</div>
<el-button @click="resetFile">清空文件</el-button>
</div>
<div class="preview-content">
<!-- PDF预览 -->
<Viewer
v-if="fileType === 'pdf'"
:key="`pdf-${previewKey}`"
:src="fileUrl"
width="100%"
height="100%"
page-scale="page-fit"
theme="light"
/>
<!-- Word预览 -->
<VueOfficeDocx
v-else-if="fileType === 'docx' || fileType === 'doc'"
:key="`docx-${previewKey}`"
:src="fileUrl"
style="width: 100%; height: 100%;"
/>
<!-- Excel预览 -->
<VueOfficeExcel
v-else-if="fileType === 'xlsx' || fileType === 'xls' || fileType === 'csv'"
:key="`excel-${previewKey}`"
:src="fileUrl"
style="width: 100%; height: 100%;"
/>
</div>
</div>
3.未上传文件时 给出提示
html
复制代码
<div v-if="!selectedFile" class="status-section">
<el-icon class="folder-icon">
<Folder/>
</el-icon>
<p class="status-text">尚未选择文档</p>
<p class="status-hint">请上传或拖放一个文档文件开始查看</p>
</div>
4.引入,使用**@vue-office 插件**
javascript
复制代码
import VueOfficeDocx from '@vue-office/docx'
import VueOfficeExcel from '@vue-office/excel'
import '@vue-office/docx/lib/index.css'
import '@vue-office/excel/lib/index.css'
完整代码
html
复制代码
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="file-preview-container">
<!-- 主要内容区域 -->
<div class="main-content">
<!-- 上传区域-->
<div class="upload-section">
<el-upload
ref="uploadRef"
class="upload-demo"
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
:accept="acceptTypes"
drag
action="#"
>
<el-icon class="upload-icon">
<UploadFilled/>
</el-icon>
<div class="el-upload__text">
点击选择或拖放文件到此区域
</div>
<div class="el-upload__tip">
支持 PDF (.pdf), Word (.docx, .doc), Excel (.xlsx, .xls, .csv) 格式
</div>
</el-upload>
<!-- 快速选择按钮 -->
<div class="quick-buttons">
<el-button
class="quick-btn pdf-btn"
@click="triggerFileSelect('pdf')"
>
<el-icon>
<Document/>
</el-icon>
<span>PDF 文档</span>
</el-button>
<el-button
class="quick-btn word-btn"
@click="triggerFileSelect('word')"
>
<el-icon>
<Document/>
</el-icon>
<span>Word 文档</span>
</el-button>
<el-button
class="quick-btn excel-btn"
@click="triggerFileSelect('excel')"
>
<el-icon>
<Document/>
</el-icon>
<span>Excel 表格</span>
</el-button>
</div>
</div>
<!-- 文档预览区域 -->
<div v-if="selectedFile" class="preview-section">
<div class="preview-header">
<div class="file-info">
<el-icon class="file-icon">
<Document/>
</el-icon>
<span class="file-name">{{ selectedFile.name }}</span>
</div>
<el-button @click="resetFile">清空文件</el-button>
</div>
<div class="preview-content">
<!-- PDF预览 -->
<Viewer
v-if="fileType === 'pdf'"
:key="`pdf-${previewKey}`"
:src="fileUrl"
width="100%"
height="100%"
page-scale="page-fit"
theme="light"
/>
<!-- Word预览 -->
<VueOfficeDocx
v-else-if="fileType === 'docx' || fileType === 'doc'"
:key="`docx-${previewKey}`"
:src="fileUrl"
style="width: 100%; height: 100%;"
/>
<!-- Excel预览 -->
<VueOfficeExcel
v-else-if="fileType === 'xlsx' || fileType === 'xls' || fileType === 'csv'"
:key="`excel-${previewKey}`"
:src="fileUrl"
style="width: 100%; height: 100%;"
/>
</div>
</div>
<!-- 底部状态显示 -->
<div v-if="!selectedFile" class="status-section">
<el-icon class="folder-icon">
<Folder/>
</el-icon>
<p class="status-text">尚未选择文档</p>
<p class="status-hint">请上传或拖放一个文档文件开始查看</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" name="filePreviewIndex" setup>
import {ref, computed} from 'vue'
import {UploadFilled, Document, Folder} from '@element-plus/icons-vue'
import {ElMessage} from 'element-plus'
import Viewer from '/@/components/Viewer.vue'
import VueOfficeDocx from '@vue-office/docx'
import VueOfficeExcel from '@vue-office/excel'
import '@vue-office/docx/lib/index.css'
import '@vue-office/excel/lib/index.css'
const uploadRef = ref()
const selectedFile = ref<File | null>(null)
const fileUrl = ref<string | ArrayBuffer>('')
const fileType = ref<string>('')
const previewKey = ref('0')
const acceptTypes = computed(() => {
return '.pdf,.doc,.docx,.xls,.xlsx,.csv'
})
// 处理文件选择
const handleFileChange = (file: any) => {
const rawFile = file.raw
if (!rawFile) return
const fileName = rawFile.name.toLowerCase()
const ext = fileName.split('.').pop()
// 验证文件类型
const validTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv']
if (!validTypes.includes(ext)) {
ElMessage.error('不支持的文件格式,请选择 PDF、Word 或 Excel 文件')
return
}
selectedFile.value = rawFile
fileType.value = ext
// 创建文件URL
if (ext === 'pdf') {
// PDF使用ArrayBuffer
const reader = new FileReader()
reader.onload = (e) => {
fileUrl.value = e.target?.result as ArrayBuffer
previewKey.value = String(Date.now())
}
reader.readAsArrayBuffer(rawFile)
} else {
// Word和Excel使用blob URL
fileUrl.value = URL.createObjectURL(rawFile)
previewKey.value = String(Date.now())
}
}
// 触发文件选择
const triggerFileSelect = (type: string) => {
const input = document.createElement('input')
input.type = 'file'
input.accept = type === 'pdf'
? '.pdf'
: type === 'word'
? '.doc,.docx'
: '.xls,.xlsx,.csv'
input.onchange = (e: any) => {
const file = e.target.files[0]
if (file) {
handleFileChange({raw: file})
}
}
input.click()
}
// 重置文件
const resetFile = () => {
if (fileUrl.value && typeof fileUrl.value === 'string') {
URL.revokeObjectURL(fileUrl.value)
}
selectedFile.value = null
fileUrl.value = ''
fileType.value = ''
previewKey.value = String(Date.now())
}
</script>
<style scoped>
* {
font-size: 18px !important;
}
</style>
<style scoped lang="scss">
.file-preview-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: #fff;
position: relative;
overflow: hidden;
}
// 主要内容区域
.main-content {
flex: 1;
padding: 40px;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(to right, #fff 0%, #f0f9ff 100%);
min-height: calc(100vh - 200px);
height: calc(100vh - 200px);
}
// 上传区域
.upload-section {
width: 100%;
max-width: 800px;
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
}
:deep(.upload-demo) {
width: 100%;
.el-upload {
width: 100%;
}
.el-upload-dragger {
width: 100%;
height: 150px;
border: 2px dashed #3b82f6;
border-radius: 12px;
background: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
&:hover {
border-color: #2563eb;
background: #f8fafc;
}
}
.upload-icon {
font-size: 64px;
color: #3b82f6;
margin-bottom: 20px;
}
.el-upload__text {
font-size: 20px;
color: #1e293b;
margin-bottom: 10px;
em {
color: #3b82f6;
font-style: normal;
font-weight: 600;
}
}
.el-upload__tip {
font-size: 16px;
color: #64748b;
margin-top: 10px;
}
}
// 快速选择按钮
.quick-buttons {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
}
.quick-btn {
padding: 15px 30px;
font-size: 18px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
border: none;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
.el-icon {
font-size: 24px;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
.pdf-btn {
background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
&:hover {
background: linear-gradient(135deg, #b91c1c 0%, #dc2626 100%);
}
}
.word-btn {
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
&:hover {
background: linear-gradient(135deg, #1d4ed8 0%, #2563eb 100%);
}
}
.excel-btn {
background: linear-gradient(135deg, #16a34a 0%, #22c55e 100%);
&:hover {
background: linear-gradient(135deg, #15803d 0%, #16a34a 100%);
}
}
// 预览区域
.preview-section {
width: 100%;
max-width: 1200px;
margin-top: 30px;
flex: 1;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
overflow: hidden;
min-height: 500px;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
.file-info {
display: flex;
align-items: center;
gap: 12px;
.file-icon {
font-size: 24px;
color: #3b82f6;
}
.file-name {
font-size: 18px;
color: #1e293b;
font-weight: 500;
}
}
}
.preview-content {
flex: 1;
overflow: auto;
background: #f1f5f9;
}
// 底部状态显示
.status-section {
text-align: center;
margin-top: 40px;
color: #64748b;
width: 100%;
.folder-icon {
font-size: 48px;
color: #cbd5e1;
margin-bottom: 15px;
}
.status-text {
font-size: 18px;
margin: 10px 0;
color: #64748b;
}
.status-hint {
font-size: 16px;
margin: 5px 0;
color: #94a3b8;
}
}
</style>
注意事项
- 文件URL需要允许跨域访问
- 大文件需要服务端支持分片加载
- PDF预览不支持文本选择
- Word文档中的复杂格式可能显示不一致