Vue中实现Word、Excel、PDF预览的详细步骤

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>

注意事项

  1. 跨域问题:确保文件服务器支持CORS
  2. 文件大小:大文件可能影响加载性能
  3. 浏览器兼容性:某些功能可能需要现代浏览器支持
  4. 安全性:验证文件来源,防止XSS攻击
  5. 移动端适配:考虑响应式设计

这些方案可以根据具体需求选择使用,建议先测试小文件确保功能正常。

相关推荐
brzhang2 小时前
干翻 Docker?WebAssembly 3.0 的野心,远不止浏览器,来一起看看吧
前端·后端·架构
lecepin3 小时前
AI Coding 资讯 2025-09-17
前端·javascript·面试
IT_陈寒3 小时前
React 18实战:7个被低估的Hooks技巧让你的开发效率提升50%
前端·人工智能·后端
树上有只程序猿3 小时前
终于有人把数据库讲明白了
前端
猩兵哥哥4 小时前
前端面向对象设计原则运用 - 策略模式
前端·javascript·vue.js
司宸4 小时前
Prompt设计实战指南:三大模板与进阶技巧
前端
RoyLin4 小时前
TypeScript设计模式:抽象工厂模式
前端·后端·typescript
华仔啊4 小时前
Vue3+CSS 实现的 3D 卡片动画,让你的网页瞬间高大上
前端·css