Thought for 1s### Vue中实现Word、Excel、PDF预览的详细步骤
1. PDF预览实现
方法一:使用PDF.js
shellscript
npm install pdfjs-dist
vue
<template>
<div class="pdf-viewer">
<canvas ref="pdfCanvas"></canvas>
<div class="controls">
<button @click="prevPage" :disabled="currentPage <= 1">上一页</button>
<span>{{ currentPage }} / {{ totalPages }}</span>
<button @click="nextPage" :disabled="currentPage >= totalPages">下一页</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'
// 设置worker路径
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js'
const pdfCanvas = ref(null)
const currentPage = ref(1)
const totalPages = ref(0)
let pdfDoc = null
const props = defineProps({
pdfUrl: {
type: String,
required: true
}
})
const loadPDF = async () => {
try {
pdfDoc = await pdfjsLib.getDocument(props.pdfUrl).promise
totalPages.value = pdfDoc.numPages
renderPage(1)
} catch (error) {
console.error('PDF加载失败:', error)
}
}
const renderPage = async (pageNum) => {
const page = await pdfDoc.getPage(pageNum)
const canvas = pdfCanvas.value
const context = canvas.getContext('2d')
const viewport = page.getViewport({ scale: 1.5 })
canvas.height = viewport.height
canvas.width = viewport.width
await page.render({
canvasContext: context,
viewport: viewport
}).promise
}
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--
renderPage(currentPage.value)
}
}
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++
renderPage(currentPage.value)
}
}
onMounted(() => {
loadPDF()
})
</script>
方法二:使用iframe嵌入
vue
<template>
<div class="pdf-viewer">
<iframe
:src="pdfUrl"
width="100%"
height="600px"
frameborder="0">
</iframe>
</div>
</template>
<script setup>
const props = defineProps({
pdfUrl: String
})
</script>
2. Excel预览实现
使用SheetJS (xlsx)
shellscript
npm install xlsx
vue
<template>
<div class="excel-viewer">
<div class="sheet-tabs">
<button
v-for="(sheet, index) in sheets"
:key="index"
@click="activeSheet = index"
:class="{ active: activeSheet === index }"
>
{{ sheet.name }}
</button>
</div>
<div class="table-container">
<table v-if="currentSheetData.length">
<thead>
<tr>
<th v-for="(header, index) in headers" :key="index">
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in currentSheetData" :key="rowIndex">
<td v-for="(cell, cellIndex) in row" :key="cellIndex">
{{ cell }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import * as XLSX from 'xlsx'
const sheets = ref([])
const activeSheet = ref(0)
const workbook = ref(null)
const props = defineProps({
excelUrl: {
type: String,
required: true
}
})
const currentSheetData = computed(() => {
if (!sheets.value[activeSheet.value]) return []
return sheets.value[activeSheet.value].data
})
const headers = computed(() => {
if (!currentSheetData.value.length) return []
return currentSheetData.value[0]
})
const loadExcel = async () => {
try {
const response = await fetch(props.excelUrl)
const arrayBuffer = await response.arrayBuffer()
workbook.value = XLSX.read(arrayBuffer, { type: 'array' })
sheets.value = workbook.value.SheetNames.map(name => {
const worksheet = workbook.value.Sheets[name]
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
return {
name,
data
}
})
} catch (error) {
console.error('Excel加载失败:', error)
}
}
onMounted(() => {
loadExcel()
})
</script>
<style scoped>
.sheet-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.sheet-tabs button {
padding: 8px 16px;
border: 1px solid #ddd;
background: #f5f5f5;
cursor: pointer;
}
.sheet-tabs button.active {
background: #007bff;
color: white;
}
.table-container {
overflow: auto;
max-height: 500px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
position: sticky;
top: 0;
}
</style>
3. Word预览实现
方法一:使用mammoth.js
shellscript
npm install mammoth
vue
<template>
<div class="word-viewer">
<div class="loading" v-if="loading">加载中...</div>
<div class="content" v-html="wordContent" v-else></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import mammoth from 'mammoth'
const wordContent = ref('')
const loading = ref(true)
const props = defineProps({
wordUrl: {
type: String,
required: true
}
})
const loadWord = async () => {
try {
loading.value = true
const response = await fetch(props.wordUrl)
const arrayBuffer = await response.arrayBuffer()
const result = await mammoth.convertToHtml({ arrayBuffer })
wordContent.value = result.value
if (result.messages.length > 0) {
console.warn('Word转换警告:', result.messages)
}
} catch (error) {
console.error('Word加载失败:', error)
wordContent.value = '<p>文档加载失败</p>'
} finally {
loading.value = false
}
}
onMounted(() => {
loadWord()
})
</script>
<style scoped>
.word-viewer {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.content {
line-height: 1.6;
font-family: 'Times New Roman', serif;
}
.content :deep(p) {
margin-bottom: 1em;
}
.content :deep(h1),
.content :deep(h2),
.content :deep(h3) {
margin-top: 1.5em;
margin-bottom: 0.5em;
}
</style>
方法二:使用在线预览服务
vue
<template>
<div class="office-viewer">
<iframe
:src="previewUrl"
width="100%"
height="600px"
frameborder="0">
</iframe>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
fileUrl: {
type: String,
required: true
},
fileType: {
type: String,
required: true // 'word', 'excel', 'pdf'
}
})
const previewUrl = computed(() => {
const encodedUrl = encodeURIComponent(props.fileUrl)
// 使用Microsoft Office Online预览
if (props.fileType === 'word' || props.fileType === 'excel') {
return `https://view.officeapps.live.com/op/embed.aspx?src=${encodedUrl}`
}
// 使用Google Docs预览
return `https://docs.google.com/gview?url=${encodedUrl}&embedded=true`
})
</script>
4. 通用文件预览组件
vue
<template>
<div class="file-preview">
<div class="file-info">
<h3>{{ fileName }}</h3>
<span class="file-type">{{ fileType.toUpperCase() }}</span>
</div>
<!-- PDF预览 -->
<PDFViewer v-if="fileType === 'pdf'" :pdf-url="fileUrl" />
<!-- Excel预览 -->
<ExcelViewer v-else-if="fileType === 'excel'" :excel-url="fileUrl" />
<!-- Word预览 -->
<WordViewer v-else-if="fileType === 'word'" :word-url="fileUrl" />
<!-- 不支持的文件类型 -->
<div v-else class="unsupported">
<p>不支持预览此文件类型</p>
<a :href="fileUrl" download>下载文件</a>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import PDFViewer from './PDFViewer.vue'
import ExcelViewer from './ExcelViewer.vue'
import WordViewer from './WordViewer.vue'
const props = defineProps({
fileUrl: {
type: String,
required: true
},
fileName: {
type: String,
default: '未知文件'
}
})
const fileType = computed(() => {
const extension = props.fileName.split('.').pop().toLowerCase()
switch (extension) {
case 'pdf':
return 'pdf'
case 'doc':
case 'docx':
return 'word'
case 'xls':
case 'xlsx':
return 'excel'
default:
return 'unknown'
}
})
</script>
<style scoped>
.file-preview {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.file-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background-color: #f8f9fa;
border-bottom: 1px solid #ddd;
}
.file-type {
background-color: #007bff;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.unsupported {
padding: 40px;
text-align: center;
color: #666;
}
</style>
5. 使用示例
vue
<template>
<div class="app">
<h1>文件预览示例</h1>
<div class="file-list">
<div v-for="file in files" :key="file.id" class="file-item">
<FilePreview
:file-url="file.url"
:file-name="file.name"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import FilePreview from './components/FilePreview.vue'
const files = ref([
{
id: 1,
name: 'sample.pdf',
url: '/files/sample.pdf'
},
{
id: 2,
name: 'data.xlsx',
url: '/files/data.xlsx'
},
{
id: 3,
name: 'document.docx',
url: '/files/document.docx'
}
])
</script>
注意事项
- 跨域问题:确保文件服务器支持CORS
- 文件大小:大文件可能影响加载性能
- 浏览器兼容性:某些功能可能需要现代浏览器支持
- 安全性:验证文件来源,防止XSS攻击
- 移动端适配:考虑响应式设计
这些方案可以根据具体需求选择使用,建议先测试小文件确保功能正常。