编写一个通用的文件预览组件,并禁止下载(pdf,xlsx,xls,ppt,docx,video,image等)

在业务开发中我们经常会需要展示我们的一些文档,很多时候我们只需要一些iframe 标签即可完成我们的任务。但如果我们要禁止文件的下载的话就需要我们自己去找组件来进行编译开发 。那么只需要看这篇文章就够了,满足你的一键预览要求

使用到的技术

  • vue-office
  • vue3.x

预览组件

1. 安装需要用到的插件

cmd 复制代码
npm install --save @vue-office/docx @vue-office/excel @vue-office/pdf @vue-office/pptx vue-demi

该组件使用vue3写法,低版本需要安装vue-composition插件进行兼容

cmd 复制代码
# vue2.6及以下使用
npm install @vue/composition-api

2. 编写预览组件

预览组件我已经编写好了,可以直接丢到项目中进行使用。大概逻辑就是

  • 引入vue-office组件并编写到template标签中
  • 组件传参并判断文件类型
  • 判断完成之后调用对应组件进行展示

FilePreview.vue

ts 复制代码
<script setup>
import { ref, onMounted, computed } from "vue";
import VueOfficePdf from "@vue-office/pdf";
import VueOfficeDocx from "@vue-office/docx";
import VueOfficeExcel from "@vue-office/excel";
import VueOfficePptx from "@vue-office/pptx";

// 定义组件属性
const props = defineProps({
  filePath: {
    type: String,
    required: true,
  },
});

const fileType = ref("");
const fileContent = ref(null);
const loading = ref(true);
const error = ref(false);
const errorMessage = ref("");

// 根据文件路径获取文件类型
const getFileType = (path) => {
  if (!path) return "";
  const extension = path.split(".").pop().toLowerCase();
  return extension;
};

// 判断文件类型
const isImage = computed(() =>
  ["jpg", "jpeg", "png", "gif", "svg", "webp"].includes(fileType.value)
);
const isPdf = computed(() => fileType.value === "pdf");
const isExcel = computed(() => ["xlsx", "xls", "et"].includes(fileType.value));
const isDocx = computed(() => ["docx", "doc", "wps"].includes(fileType.value));
const isVideo = computed(() => ["mp4", "webm", "ogg"].includes(fileType.value));
const isText = computed(() =>
  ["txt", "json", "md", "csv"].includes(fileType.value)
);
const isDps = computed(() => ["dps", "ppt", "pptx"].includes(fileType.value));

// 调试信息
const debugInfo = computed(() => {
  return {
    fileType: fileType.value,
    isExcel: isExcel.value,
    filePath: props.filePath,
  };
});

// 加载文件内容
const loadFile = async () => {
  loading.value = true;
  error.value = false;
  errorMessage.value = "";

  try {
    fileType.value = getFileType(props.filePath);
    console.log("文件类型:", fileType.value, "是否Excel:", isExcel.value);

    if (isText.value) {
      // 加载文本文件
      const response = await fetch(props.filePath);
      if (!response.ok)
        throw new Error(`Failed to load file: ${response.statusText}`);
      fileContent.value = await response.text();
    } else if (fileType.value === "xls") {
      // 特殊处理xls文件
      console.log("检测到xls文件,使用特殊处理");
      // 添加时间戳或随机参数以避免缓存问题
      fileContent.value = `${props.filePath}?t=${new Date().getTime()}`;
    } else {
      // 对于其他类型的文件,直接使用文件路径
      fileContent.value = props.filePath;
    }
  } catch (err) {
    error.value = true;
    errorMessage.value = err.message || "Failed to load file";
    console.error("Error loading file:", err);
  } finally {
    loading.value = false;
  }
};

// 组件挂载时加载文件
onMounted(() => {
  loadFile();
});
</script>

<template>
  <div class="file-preview-container">
    <div v-if="loading" class="loading-container">
      <div class="loading-spinner"></div>
      <p>加载中...</p>
    </div>

    <div v-else-if="error" class="error-container">
      <p>{{ errorMessage || "文件加载失败" }}</p>
    </div>

    <div v-else class="preview-content">
      <!-- PDF 预览 -->
      <vue-office-pdf
        v-if="isPdf"
        :src="fileContent"
        :disable-download="true"
        class="preview-item"
      />

      <!-- Excel 预览 -->
      <vue-office-excel
        v-else-if="isExcel"
        :src="fileContent"
        :disable-download="true"
        :options="{
          xls: fileType === 'xls' ? true : false,
        }"
        class="preview-item"
      />

      <!-- 调试信息 -->
      <!-- <div v-if="isExcel" class="debug-info">
        <p>调试信息: {{ JSON.stringify(debugInfo) }}</p>
      </div> -->

      <!-- Word 预览 -->
      <vue-office-docx
        v-else-if="isDocx"
        :src="fileContent"
        :disable-download="true"
        class="preview-item"
      />

      <!-- PPT/DPS 预览 -->
      <vue-office-pptx
        v-else-if="isDps"
        :src="fileContent"
        :disable-download="true"
        class="preview-item"
      />

      <!-- 图片预览 -->
      <img
        v-else-if="isImage"
        :src="fileContent"
        alt="Image preview"
        class="preview-item image-preview"
        @contextmenu.prevent
        @dragstart.prevent
        draggable="false"
      />

      <!-- 视频预览 -->
      <video
        v-else-if="isVideo"
        controls
        class="preview-item video-preview"
        @contextmenu.prevent
        @dragstart.prevent
        controlsList="nodownload"
      >
        <source :src="fileContent" :type="`video/${fileType}`" />
        您的浏览器不支持视频标签
      </video>

      <!-- 文本预览 -->
      <pre v-else-if="isText" class="preview-item text-preview">{{
        fileContent
      }}</pre>

      <!-- 不支持的文件类型 -->
      <div v-else class="unsupported-file">
        <p>不支持预览此类型的文件 ({{ fileType }})</p>
      </div>
    </div>
  </div>
</template>

<style scoped>
.file-preview-container {
  width: 100%;
  height: 100%;
  min-height: 400px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #f5f5f5;
  border-radius: 8px;
  overflow: hidden;
}

.loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-radius: 50%;
  border-top-color: #3498db;
  animation: spin 1s ease-in-out infinite;
  margin-bottom: 10px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.error-container {
  color: #e74c3c;
  text-align: center;
  padding: 20px;
}

.preview-content {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.preview-item {
  max-width: 100%;
  max-height: 100%;
  width: 100%;
  height: 100%;
  overflow: auto;
}

.image-preview {
  object-fit: contain;
  user-select: none;
}

.video-preview {
  max-height: 100%;
  background-color: #000;
}

.text-preview {
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: #fff;
  padding: 20px;
  white-space: pre-wrap;
  font-family: monospace;
  box-sizing: border-box;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}

.unsupported-file {
  padding: 20px;
  text-align: center;
  color: #7f8c8d;
}
</style>

使用组件

直接路径传参数即可

ts 复制代码
<script setup>
import { ref, onMounted } from "vue";
import FilePreview from "./FilePreview.vue";

const filePath = ref("");
const error = ref(false);
const errorMessage = ref("");

// 从URL参数中获取文件路径
const getFilePathFromUrl = () => {
  const urlParams = new URLSearchParams(window.location.search);
  const fileParam = urlParams.get("file");

  if (!fileParam) {
    error.value = true;
    errorMessage.value = "未指定文件路径,请使用 ?file=xxx 参数";
    return "";
  }

  return decodeURIComponent(fileParam);
};

onMounted(() => {
  filePath.value = getFilePathFromUrl();
});
</script>

<template>
  <div class="file-viewer">
    <div v-if="error" class="error-message">
      <h2>{{ errorMessage }}</h2>
      <p>请使用格式: index.html?file=文件路径</p>
    </div>

    <FilePreview v-else-if="filePath" :filePath="filePath" />
  </div>
</template>

<style scoped>
.file-viewer {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.error-message {
  text-align: center;
  padding: 20px;
  color: #e74c3c;
  max-width: 600px;
}
</style>

最后

这只是很粗略的一个模板,大部分类型我都有进行测试。但有遗漏的话还请大家指出我进行完善,但别骂人谢谢大家。

相关推荐
酉鬼女又兒8 分钟前
零基础快速入门前端Web存储(sessionStorage & localStorage)知识点详解与蓝桥杯考点应用(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·职场和发展·蓝桥杯·html
DanCheOo13 分钟前
# 从"会用 AI"到"架构 AI":高级前端的认知升级
前端·ai编程
社恐的下水道蟑螂15 分钟前
前端面试必问 Git 通关指南:常用命令速查 + merge/rebase 深度辨析,看完再也不慌
前端·git·面试
None32124 分钟前
NestJS 流式文件上传实践:从 Multer 到 Busboy 的进阶之路
前端·后端
海浪浪26 分钟前
Symbol 产生的背景以及应用场景
前端·javascript
DROm RAPS35 分钟前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端
OpenTiny社区35 分钟前
GenUI SDK v1.1.0 正式发布|全端体验革新,能力与稳定性进阶
前端·ai编程
IAUTOMOBILE37 分钟前
Code Marathon 项目源码解析与技术实践
java·前端·算法
Flying pigs~~37 分钟前
基于Deepseek大模型API完成文本分类预测功能
java·前端·人工智能·python·langchain·deepseek
名字很费劲1 小时前
vue项目,刷新后出现404错误,怎么解决
前端·javascript·vue·404