0


前端文件预览整合(一)

在 Web 应用中,文件预览功能是一个常见且实用的需求。
最下方有源码,大佬可以跳过简单介绍可以直接拉到下方查看。

方案 1 - kkFileView 服务

kkFile 文件预览是一款开源的文档在线预览项目。项目使用流行的 spring boot 搭建,易上手和部署,基本支持主流办公文档的在线预览,如 doc,docx,Excel,pdf,txt,zip,rar,图片等等。。

  1. 无需前端代码支持:kkFileView 是一个后端服务解决方案,不需要前端大量代码文件支持。
  2. 支持多种文件类型:kkFileView 支持 图片.docx.xlsx.pdf 等常见文件类型。

方案 2 - 前端预览插件(文后附 vue 代码

前端预览插件是一个更轻量级的解决方案,它不需要服务端支持,只需在浏览器中加载文件预览组件即可。目前仅支持docx、xlsx、xls、pdf、jpg、jpeg、png、txt、MP4、MP3格式,其他后缀可继续扩展

  1. 无需后端支持:前端预览插件不需要服务端支持,只需在浏览器中加载文件预览组件即可。
  2. 轻量:前端预览插件仅依赖前端框架,不依赖于任何后端服务。
  3. 灵活:前端预览插件支持各种文件类型,包括图片、文档、音视频等。

方案 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

文件类型,并且可以根据需要轻松扩展以支持更多文件类型。

文件列表

20230926164408

word文档

20230926164408

excel预览(xlsx和xls不一致,xls无空白行数据及样式)

2023092616440820230926164408

txt预览

标签: 前端

本文转载自: https://blog.csdn.net/qq_43630714/article/details/143233290
版权归原作者 大张lucio 所有, 如有侵权,请联系我们删除。

“前端文件预览整合(一)”的评论:

还没有评论