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

13 阅读3分钟

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

使用到的技术

  • vue-office
  • vue3.x

预览组件

1. 安装需要用到的插件

npm install --save @vue-office/docx @vue-office/excel @vue-office/pdf @vue-office/pptx vue-demi

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

# vue2.6及以下使用
npm install @vue/composition-api

2. 编写预览组件

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

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

FilePreview.vue

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

使用组件

直接路径传参数即可

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

最后

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