在 Web 应用中,文件预览功能是一个常见且实用的需求。
最下方有源码,大佬可以跳过简单介绍可以直接拉到下方查看。
方案 1 - kkFileView 服务
kkFile 文件预览是一款开源的文档在线预览项目。项目使用流行的 spring boot 搭建,易上手和部署,基本支持主流办公文档的在线预览,如 doc,docx,Excel,pdf,txt,zip,rar,图片等等。。
- 无需前端代码支持:kkFileView 是一个后端服务解决方案,不需要前端大量代码文件支持。
- 支持多种文件类型:kkFileView 支持
图片
、.docx
、.xlsx
和.pdf
等常见文件类型。
方案 2 - 前端预览插件(文后附 vue 代码)
前端预览插件是一个更轻量级的解决方案,它不需要服务端支持,只需在浏览器中加载文件预览组件即可。目前仅支持docx、xlsx、xls、pdf、jpg、jpeg、png、txt、MP4、MP3格式,其他后缀可继续扩展。
- 无需后端支持:前端预览插件不需要服务端支持,只需在浏览器中加载文件预览组件即可。
- 轻量:前端预览插件仅依赖前端框架,不依赖于任何后端服务。
- 灵活:前端预览插件支持各种文件类型,包括图片、文档、音视频等。
方案 2 的实现方式
1. 组件结构
首先,我们定义一个 Vue 组件,用于容纳文件列表和预览对话框。
<template><divclass="filepreview-container"><divclass="file-list"><divv-for="(item, index) in fileList":key="index"class="file-item"@click="handleShowFile(item)">
{{ item.src }}
</div></div><divv-if="showFile"class="file-preview-dialog"@click="handleClose"><!-- 这里空白点击事件是为了阻止触发父元素点击事件,避免关闭预览框 --><divclass="file-preview-content"@click.stop="()=>{}"><divclass="file-box"><divv-if="!supportedTypes.includes(extractFileType(targetFileSrc))"class="empty-box">
在线预览服务当前仅支持docx/xlsx/xls/pdf/jpg/jpeg/png/txt/mp4/mp3
</div><divv-else-if="extractFileType(targetFileSrc) === 'xls'"class="file-content xls-content"><divid="tabs-container"><divclass="tabs-header"><!-- 动态生成sheet标签页 --><div:class="['tab-item', { active: activeSheetName === itemSheetName }]"v-for="(item, itemSheetName) in xlsData":key="itemSheetName"@click="activeSheetName = itemSheetName">
{{ itemSheetName }}
</div></div><divclass="tabs-content"><!-- 显示当前选中的表格 --><templatev-if="activeSheetName && xlsData[activeSheetName]"><tablev-if="xlsData[activeSheetName].length > 0"class="data-table"><thead><tr><!-- 表头 --><thv-for="(item, key) in xlsData[activeSheetName][0]":key="key">
{{ key }}
</th></tr></thead><tbody><!-- 表格内容 --><trv-for="row in xlsData[activeSheetName]":key="row.id"><tdv-for="(cell, key) in row":key="key">
{{ cell }}
</td></tr></tbody></table><divv-else>当前Sheet页无数据</div></template><divv-else>请选择一个Sheet页</div></div></div></div><VueOfficeDocxv-else-if="extractFileType(targetFileSrc) === 'docx'":src="targetFileSrc"class="file-content docx-content"/><VueOfficeExcelv-else-if="extractFileType(targetFileSrc) === 'xlsx'":src="targetFileSrc"class="file-content xlsx-content"/><VueOfficePdfv-else-if="extractFileType(targetFileSrc) === 'pdf'":src="targetFileSrc"class="file-content pdf-content"/><divv-else-if="['jpeg', 'png', 'jpg'].includes(extractFileType(targetFileSrc))"class="file-content img-content"><img:src="targetFileSrc":alt="targetFileSrc"/></div><divv-else-if="extractFileType(targetFileSrc) === 'txt'"class="file-content txt-content"><pre><code>{{ txtData }}</code></pre></div><divv-else-if="extractFileType(targetFileSrc) === 'mp3'"class="file-content audio-content"><!-- 音频文件 --><audio:src="targetFileSrc"controlswidth="100%"height="30px">
您的浏览器不支持 audio 标签。
</audio></div><divv-else-if="extractFileType(targetFileSrc) === 'mp4'"class="file-content video-content"><!-- 视频文件 --><videocontrolswidth="auto"height="100%"><source:src="targetFileSrc"type="video/mp4"/>
您的浏览器不支持 Video 标签。
</video></div></div><divclass="file-box-btns"><div@click="downLoadFile">下载</div><div@click="handleClose">取消</div></div></div></div></div></template>
2. 使用第三方库
为了处理不同类型的文件预览,我们将使用
@vue-office
、
xlsx-js-style
。
安装依赖
npminstall @vue-office/docx @vue-office/excel @vue-office/pdf xlsx-js-style
引入并注册组件
在 Vue 组件中,我们需要引入并注册这些第三方库提供的组件。
<script setup name="FilePreview">import{ ref, computed, defineProps }from"vue"import{ getToken }from"@/utils/auth"import VueOfficeDocx from"@vue-office/docx"import"@vue-office/docx/lib/index.css"import VueOfficeExcel from"@vue-office/excel"import"@vue-office/excel/lib/index.css"import VueOfficePdf from"@vue-office/pdf"import*asXLSXfrom"xlsx-js-style"// 支持的文件类型数组const supportedTypes =["docx","xlsx","xls","pdf","jpg","jpeg","png","txt","mp4","mp3"]// 封装一个fetch方法,通过文件路径获取二进制文件内容constgetFileArrayBufferBySrc=async(src, useLocalToken =false)=>{const headers =newHeaders()if(useLocalToken){
headers.set("Authorization",`Bearer ${getToken()}`)}const res =awaitfetch(src,{ headers })if(!res.ok){thrownewError("请求失败")}returnawait res.arrayBuffer()}// 提取文件类型函数,通过文件路径获取文件扩展名constextractFileType=path=>{const dotIndex = path.lastIndexOf(".")return dotIndex !==-1? path.substring(dotIndex +1):"无文件扩展名"}// 验证URL是否有效的函数constisValidUrl=url=>{const pattern =newRegExp('^(https?:\\/\\/)?'+// 协议'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+// 域名'((\\d{1,3}\\.){3}\\d{1,3}))'+// IP'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+// 端口和路径'(\\?[;&a-z\\d%_.~+=-]*)?'+// 查询字符串'(\\#[-a-z\\d_]*)?$','i');// 锚点return!!pattern.test(url);}// 定义组件属性// list可以是字符串数组[''],也可以是object数组[{name: '', src: ''}]const props =defineProps({
list:{
type: Array,default:()=>[]}})// 文件预览对话框显示状态const showFile =ref(false)// 当前预览文件的源路径const targetFileSrc =ref("")// 当前活动的Sheet名称,用于Excel文件预览const activeSheetName =ref(undefined)// Excel文件数据const xlsData =ref({})// 文本文件数据const txtData =ref(undefined)// 处理文件列表,根据URL或对象生成统一的文件列表格式const fileList =computed(()=>{return props.list.map(item=>{if(typeof item ==="string"){const lastSlashIndex = item.lastIndexOf("/")const dotIndex = item.lastIndexOf(".")if(lastSlashIndex !==-1&& dotIndex !==-1&& lastSlashIndex < dotIndex){const fileName = item.substring(lastSlashIndex +1, dotIndex)const fileType = item.substring(dotIndex +1)return{ name: fileName, src: item, filetype: fileType }}else{return{ name:"未知文件类型", src: item, filetype:"unknown"}}}elseif(typeof item ==="object"&& item !==null&&"src"in item){const fileName ="name"in item ? item.name :"unknown"const fileType =extractFileType(item.src)return{ name: fileName, src: item.src, filetype: fileType }}else{return{ name:"unknown", src:"unknown", filetype:"unknown"}}})})// 处理文件预览consthandleShowFile=asyncitem=>{
showFile.value =true
targetFileSrc.value = item.src
if(item.filetype ==="xls"){const arrayBuffer =awaitgetFileArrayBufferBySrc(item.src,false)const data =newUint8Array(arrayBuffer)const workbook =XLSX.read(data,{ type:"buffer"})
activeSheetName.value = workbook.SheetNames[0]
workbook.SheetNames.forEach(sheetName=>{const worksheet = workbook.Sheets[sheetName]
xlsData.value[sheetName]=XLSX.utils.sheet_to_json(worksheet)})}elseif(item.filetype ==="txt"){const arrayBuffer =awaitgetFileArrayBufferBySrc(item.src,false)const decoder =newTextDecoder("utf-8")
txtData.value = decoder.decode(arrayBuffer)}}// 处理文件下载constdownLoadFile=()=>{if(!isValidUrl(targetFileSrc.value)){
console.error("下载路径不正确.")return}fetch(targetFileSrc.value,{
method:"GET",
headers:{
Authorization:`Bearer ${getToken()}`,"Content-Type":"application/x-www-form-urlencoded"}}).then(response=>{if(!response.ok){thrownewError("网络连接失败")}// 将 Response 转换为 Blobreturn response.blob()}).then(data=>{const reader =newFileReader()// 读取 blob 数据
reader.readAsDataURL(data)// 读取 blob 数据为 Data URL
reader.onloadend=function(){const a = document.createElement("a")
a.href = reader.result
// 设置为'_blank'以在新窗口或标签页中打开
a.target ="_blank"// 安全问题
a.rel ="noopenner noreferrer"const lastSlashIndex = targetFileSrc.value.lastIndexOf("/")const fileName = targetFileSrc.value.substring(lastSlashIndex +1)
a.download = fileName // 设置文件名
a.click()// 触发下载handleClose()}}).catch(error=>{
console.error("下载失败:", error)})}// 关闭文件预览对话框consthandleClose=()=>{
showFile.value =false
targetFileSrc.value =undefined
activeSheetName.value =undefined
xlsData.value ={}
txtData.value =undefined}</script>
3. 样式
最后,我们添加一些 CSS 样式来美化我们的文件预览插件。
<style lang="scss" scoped>
$border-color: #e4e7ed;
$hover-bg: #f5f7fa;
$hover-color: #69b4ff;
$active-color: #409eff;
$file-preview-dialog-width: 100vw;
$file-preview-dialog-height: 100vh;
.filepreview-container {
width: 100%;
max-width: 1000px;
overflow: auto;
.file-list {
.file-item {
border: 1px solid $border-color;
line-height: 2;
margin-bottom: 10px;
position: relative;
// 省略号
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 0 10px;
// hover
transition: background-color 0.3s, color 0.3s;
&:hover {
background-color: $hover-bg;
cursor: pointer;
color: $hover-color;
}
}
}
.file-preview-dialog {
width: $file-preview-dialog-width;
height: $file-preview-dialog-height;
background-color: rgba(0, 0, 0, 0.4);
overflow: hidden;
position: fixed;
left: 0;
top: 0;
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
.file-preview-content {
width: 80vw;
height: 90vh;
background-color: #fff;
border-radius: 5px;
padding: 30px;
display: flex;
flex-flow: column;
.file-box {
flex: 1;
height: calc(90vh - 80px - 60px);
.file-content {
width: 100%;
height: 100%;
display: flex;
flex-flow: column;
overflow: auto;
background-color: #808080;
}
.xls-content {
// 表格table的基础样式
#tabs-container {
width: 100%;
height: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
border: 1px solid $border-color;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.tabs-header {
width: 100%;
display: flex;
gap: 5px;
background-color: #f2f2f2;
padding: 5px 5px 0;
.tab-item {
padding: 8px 16px;
cursor: pointer;
border: 1px solid $border-color;
border-bottom: none;
color: #606266;
font-size: 14px;
line-height: 1.5;
transition: all 0.3s;
&.active {
background-color: white;
position: relative;
top: -1px; /* 下沉效果 */
border-bottom-color: transparent;
color: $active-color;
font-weight: bold;
}
}
}
.tabs-content {
width: 100%;
height: calc(100% - 36px); /* 减去头部高度 */
padding: 10px;
overflow: auto;
border: 1px solid $border-color;
border-top: none;
.data-table {
width: 100%;
border-collapse: collapse;
overflow: auto;
font-size: 14px;
color: #606266;
th,
td {
border: 1px solid $border-color;
padding: 8px;
text-align: left;
min-width: 100px;
min-height: 30px;
background-color: #fff;
vertical-align: middle;
}
th {
background-color: #f2f2f2;
color: #606266;
font-weight: bold;
}
tr:hover {
background-color: $hover-bg;
}
}
}
}
}
.img-content {
> img {
width: 100%;
height: auto;
}
}
.txt-content {
font-size: 15px;
line-height: 1.5;
background-color: #fff;
}
.audio-content {
display: flex;
justify-content: center;
align-items: center;
}
.empty-box {
color: #aaaaaa;
fill: currentColor;
width: 100%;
height: 100%;
vertical-align: top;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
}
.file-box-btns {
display: flex;
flex-flow: row;
justify-content: center;
margin: 20px 0;
> div {
margin: 0 20px;
background-color: $active-color;
color: #fff;
line-height: 40px;
padding: 0 20px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
&:hover {
background-color: lighten($active-color, 10%);
color: #fff;
}
}
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.filepreview-container {
max-width: 100%;
}
.file-preview-dialog {
width: 100%;
height: 100%;
.file-preview-content {
width: 90%;
height: 80%;
padding: 20px;
.file-box {
height: calc(80% - 40px - 40px);
.file-content {
height: 100%;
}
}
}
}
}
</style>
通过以上步骤,我们实现了一个功能齐全的前端文件预览插件。用户可以通过点击文件列表中的文件来查看其内容,并且可以下载文件。这个插件支持
.docx
、
.xlsx
和
.pdf
文件类型,并且可以根据需要轻松扩展以支持更多文件类型。
文件列表
word文档
excel预览(xlsx和xls不一致,xls无空白行数据及样式)
txt预览
版权归原作者 大张lucio 所有, 如有侵权,请联系我们删除。