vue3+element-plus上传文件,预览文件

vue3+ts+element-plus上传文件,预览文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

场景:使用element-plus的el-upload标签,手动上传文件,可预览docx,xlsx,pdf,jpg,jpeg,png(本地资源以及网络资源)。

1、使用el-upload标签

在这里插入图片描述

检查上传文件的文件格式与大小

在这里插入图片描述

上传的附件信息在fileList中,组装接口所需数据进行上传

使用docx-preview插件预览docx类型的文件

在这里插入图片描述
在这里插入图片描述

使用xlsx插件预览xlsx文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里遇到了问题,引入xlsx插件的时候出现"export ‘default’ (imported as ‘XLSX’) was not found in 'xlsx’报错
解决:

直接将import XLSX from 'xlsx’改为import * as XLSX from 'xlsx/xlsx.mjs’即可

图片预览

在这里插入图片描述
在这里插入图片描述

pdf预览

在这里插入图片描述
在这里插入图片描述

完整代码
上传页面

<template>
  <el-dialog
    v-model="uploadDialogVisible"
    title="上传"
    width="800px"
    label-width="100px"
    @close="uploadDialogVisible = false"
  >
    <el-upload
      ref="uploadRef"
      v-model:file-list="fileList"
      class="upload-demo"
      drag
      :multiple="true"
      :auto-upload="false"
      :accept="props.allowType"
      :limit="props.limit"
      :before-upload="beforeAvatarUpload"
      :on-preview="previewFun"
    >
      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
      <div class="el-upload__text">
        <em>选择文件</em>
      </div>
      <template #tip>
        <div class="el-upload__tip">
          支持格式:{{ props.allowType }};限制大小{{ props.size }}M
        </div>
      </template>
    </el-upload>
    <template #footer>
      <span class="dialog-footer">
        <el-button round @click="cancelFun">取消</el-button>
        <el-button round type="primary" @click="submitFun">确定</el-button>
        <el-button round type="primary" @click="getFileList"
          >获取文件</el-button
        >
      </span>
    </template>
  </el-dialog>
  <!-- 查看 -->
  <viewer ref="fileViewerRef" :dialog-doc="dialogDoc" />
</template>
<script setup lang="ts">
/* eslint-disable */
import { defineProps, defineEmits, ref, reactive, computed } from "vue";
import type {
  FormInstance,
  UploadProps,
  UploadUserFile,
  UploadRawFile
} from "element-plus";
import { ElMessage } from "element-plus";
import { UploadFilled } from "@element-plus/icons-vue";
import { api } from "@/api";
import SparkMD5 from "spark-md5";
import Viewer from "./viewer.vue";

interface Props {
  uploadDialogVisible?: boolean;
  allowType?: string;
  limit?: number;
  fileList?: any;
  size?: number;
  fileExtendId?: string;
  fileCategory?: number;
  folderName?: string;
  systemSource?: string;
  uploadUrl?: string;
}
//props
const props: any = withDefaults(defineProps<Props>(), {
  uploadDialogVisible: false,
  allowType: "doc,docx,jpg,jpeg,png,xlsx,pdf",
  limit: 5,
  size: 5,
  fileExtendId: "",
  fileCategory: 2,
  folderName: "ceshi",
  systemSource: "ceshi",
  uploadUrl: "http://192.168.188.3:7001"
});

const uploadDialogVisible = computed(() => {
  return props.uploadDialogVisible;
});

const uploadRef = ref<FormInstance>();
const fileList = ref(([] as any));
const fileExtendId = ref("682D0E35121A4D4E831714CDACD5A18E");

const beforeAvatarUpload = () => {
  const flag = ref(true);
  fileList.value.forEach((item: any) => {
    console.log(item);
    const type = item.name.split(".")[1];
    if (props.allowType.indexOf(type) == -1) {
      ElMessage({
        type: "error",
        message: `格式错误,支持格式:${props.allowType}!`
      });
      flag.value = false;
      return;
    } else if (item.size / 1024 / 1024 > props.size) {
      ElMessage.error(`文件最大${props.size}MB!`);
      flag.value = false;
      return;
    }
  });
  return flag.value;
};

//取消
const cancelFun = () => {
  emit("cancel", false);
};

const submitFun = (formEl: FormInstance | undefined) => {
  // 判断是否有文件需要上传
  if (fileList.value.length) {
    const flag = beforeAvatarUpload();
    if (!flag) {
      return;
    }
    // 组合数据
    const params = new FormData();
    const fileConfigs: any = [];
    if (fileList.value.length) {
      fileList.value.forEach((item: any, index: number) => {
        // 判断一下是不是新上传的
        if (item.id) {
          fileConfigs.push({
            fileId: item.id,
            fileName: item.name,
            sort: ++index, // 必须从1开始,依次12345往下
            md5: ""
          });
        } else {
          params.append("file", item.raw);
          const spark = new SparkMD5.ArrayBuffer();
          spark.append(item.raw);
          const md5 = spark.end();
          fileConfigs.push({
            fileId: "",
            fileName: item.name,
            sort: ++index, // 必须从1开始,依次12345往下
            md5: md5
          });
      };
      }
      );
    }
    params.append("fileExtendId", fileExtendId.value);
    params.append("fileCategory", props.fileCategory);
    params.append("folderName", props.folderName);
    params.append("systemSource", props.systemSource);
    params.append("FileConfigs", JSON.stringify(fileConfigs));
    console.log(params);
    api.adminCenter.UploadFiles(params).then((res: any) => {
      if (res.status.code == 200) {
        fileExtendId.value = res.result.fileExtendId;
        // 拿到了组合id,继续做业务
      }
    });
  } else {
    console.log("直接做业务");
  }
};

// 获取附件
const getFileList = () => {
  api.adminCenter
    .GetFileList({
      fileExtendIds: fileExtendId.value
    })
    .then((res: any) => {
      if (res.status.code == 200) {
        res.result.forEach((ele: any) => {
          ele.name = ele.fileOldName;
          fileList.value.push(ele);
        });
      } else {
      }
    });
};

let dialogDoc: any = ref(false);
const fileViewerRef = ref<any>(null);
const previewFun = (file: any) => {
  console.log(file.halfPath);
  dialogDoc.value = true;
  let data = file;
  // 上传过的文件组成完成的网络路径
  if (file.halfPath) {
    data.src = `${props.uploadUrl}${file.halfPath}`;
  }

  const suffix = data.name.split(".")[1];
  if (suffix == "docx") {
    fileViewerRef.value?.viewDocx(data);
  } else if (suffix == "xlsx") {
    fileViewerRef.value?.viewXlsx(data);
  } else if (suffix == "jpg" || suffix == "jpeg" || suffix == "png") {
    fileViewerRef.value?.viewImg(data);
  } else if (suffix == "pdf") {
    fileViewerRef.value?.viewPdf(data);
  }
};
// 声明emit
const emit = defineEmits(["cancel"]);
</script>

./viewer.vue文件

<template>
  <!-- doc -->
  <el-dialog
    v-model="dialogDocxValue"
    :title="dialogTitle"
    width="80%"
    @close="dialogDocxClose"
  >
    <div ref="docxRef" class="word-div"></div>
  </el-dialog>

  <!-- xlsx -->
  <el-dialog
    v-model="dialogXlsxValue"
    :title="dialogTitle"
    width="80%"
    @close="dialogXlsxClose"
  >
    <div ref="xlsxRef" class="xlsx-div">
      <el-tabs v-model="activeName" type="border-card">
        <el-tab-pane
          v-for="(item, index) in excelSheet"
          :key="index"
          :label="item.name"
          :name="item.name"
        >
          <div class="table" v-html="item.html"></div>
        </el-tab-pane>
      </el-tabs>
    </div>
  </el-dialog>

  <!-- 图片 -->
  <el-dialog
    v-model="dialogImgValue"
    :title="dialogTitle"
    width="80%"
    @close="dialogImgClose"
  >
    <div class="img-div">
      <el-image :src="fileData.src" :alt="fileData.fileOldName" />
    </div>
  </el-dialog>

  <!-- pdf -->
  <el-dialog
    v-model="dialogPdfValue"
    :title="dialogTitle"
    width="80%"
    @close="dialogPdfClose"
  >
    <div class="pdf-div">
      <iframe
        id="pdfRef"
        :src="iframeUrl"
        frameborder="0"
        style="width: 100%; height: 99%"
      ></iframe>
    </div>
  </el-dialog>
</template>
<script setup lang="ts">
import axios from "axios";
import { defineProps, ref, reactive, computed, nextTick } from "vue";
import { renderAsync } from "docx-preview";
import * as XLSX from "xlsx";
interface Props {
  dialogDocx?: boolean;
  dialogXlsx?: boolean;
  dialogTitle?: string;
}
const props: any = withDefaults(defineProps<Props>(), {
  dialogDocx: false,
  dialogTitle: "",
  dialogXlsx: false
});
const dialogDocxValue: any = ref(false);
let dialogTitle = computed(() => {
  return props.dialogTitle;
});
const fileHtml = ref("");

const docxRef = ref<any>();
// doc 文档预览
const viewDocx = (data: any) => {
  docxRef.value = "";
  dialogDocxValue.value = true;
  console.log(data);
  if (data.src) {
    // 已上传的文件
    axios({
      url: data.src,
      method: "get",
      responseType: "blob"
    }).then((res) => {
      console.log(res);
      if (res.status == 200) {
        const content = res.data;
        const blob = new Blob([content]);
        nextTick(() => {
          dialogDocxValue.value = true;
          renderAsync(blob, docxRef.value);
          dialogTitle = data.fileOldName || data.name;
        });
      }
    });
  } else {
    // 本地文件
    const blob = new Blob([data.raw]);
    nextTick(() => {
      dialogDocxValue.value = true;
      renderAsync(blob, docxRef.value);
      dialogTitle = data.fileOldName || data.name;
    });
  }
};
const dialogXlsxValue: any = ref(false);
const excelSheet: any = ref([]);
const activeName = ref("");
const dialogDocxClose = () => {
  dialogDocxValue.value = false;
  docxRef.value = "";
};
// xlsx 预览
const viewXlsx = (data: any) => {
  dialogXlsxValue.value = true;
  console.log(data);
  if (data.src) {
    axios({
      url: data.src,
      method: "get",
      responseType: "blob"
    }).then((res) => {
      console.log(res);
      if (res.status == 200) {
        const content = res.data;
        // const blob = new Blob(content);
        const reader = new FileReader();
        reader.readAsArrayBuffer(content);
        reader.onload = function (loadEvent: any) {
          const arrayBuffer = loadEvent.target["result"];
          const workbook = XLSX.read(new Uint8Array(arrayBuffer), {
            type: "array"
          });
          const list = [];
          const sheetNames = workbook.SheetNames;
          activeName.value = sheetNames[0];
          for (const p of sheetNames) {
            let html = "";
            try {
              html = XLSX.utils.sheet_to_html(workbook.Sheets[p]);
            } catch (e) {
              html = "";
            }
            const map = {
              name: p,
              html: html
            };
            list.push(map);
          }
          excelSheet.value = list;
          dialogTitle = data.fileName || data.name;
        };
      }
    });
  } else {
    const blob = new Blob([data.raw]);
    const reader = new FileReader();
    reader.readAsArrayBuffer(blob);
    reader.onload = function (loadEvent: any) {
      const arrayBuffer = loadEvent.target["result"];
      const workbook = XLSX.read(new Uint8Array(arrayBuffer), {
        type: "array"
      });
      const list = [];
      const sheetNames = workbook.SheetNames;
      activeName.value = sheetNames[0];
      for (const p of sheetNames) {
        let html = "";
        try {
          html = XLSX.utils.sheet_to_html(workbook.Sheets[p]);
        } catch (e) {
          html = "";
        }
        const map = {
          name: p,
          html: html
        };
        list.push(map);
      }
      excelSheet.value = list;
      dialogTitle = data.fileName || data.name;
    };
  }
};

const dialogXlsxClose = () => {
  dialogXlsxValue.value = false;
  excelSheet.value = "";
  activeName.value = "";
};
const fileData: any = ref({});
const dialogImgValue: any = ref(false);
// 图片预览
const viewImg = (data: any) => {
  if (data.src) {
    // 已上传的图片
    fileData.value = data;
  } else {
    // 本地图片
    const freader = new FileReader();
    freader.readAsDataURL(data.raw);
    freader.onload = (e: any) => {
      fileData.value = {
        src: e.target.result,
        id: new Date(),
        fileName: data.fileOldName || data.name
      };
    };
  }
  dialogTitle = data.fileOldName || data.name;
  dialogImgValue.value = true;
};
const dialogImgClose = () => {
  dialogImgValue.value = false;
};

const dialogPdfValue: any = ref(false);
const iframeUrl: any = ref("");
const pdfRef = ref<any>();
const viewPdf = (data: any) => {
  if (data.src) {
    axios({
      url: data.src,
      method: "get",
      responseType: "blob"
    }).then((res) => {
      if (res.status == 200) {
        // 把文件流转化为url
        iframeUrl.value = URL.createObjectURL(res.data);
        dialogPdfValue.value = true;
      }
    });
  } else {
    iframeUrl.value = URL.createObjectURL(data.raw);
    dialogPdfValue.value = true;
  }
};
const dialogPdfClose = () => {
  dialogPdfValue.value = false;
};
defineExpose({
  viewDocx,
  viewXlsx,
  viewImg,
  viewPdf
});
</script>
<style scoped lang="scss">
.word-div {
  height: calc(70vh);
  overflow: auto;
}
.xlsx-div {
  height: calc(70vh);
  overflow: auto;
}
.img-div {
  height: calc(70vh);
  overflow: auto;
  img {
    width: 100%;
    height: 100%;
  }
}
.pdf-div {
  height: calc(70vh);
  overflow: auto;
}
</style>
<style lang="scss">
.xlsx-div {
  .table-html-wrap table {
    border-right: 1px solid #fff;
    border-bottom: 1px solid #e8eaec;
    border-collapse: collapse;
    // margin: auto;
  }

  .table-html-wrap table td {
    border-left: 1px solid #e8eaec;
    border-top: 1px solid #e8eaec;
    white-space: wrap;
    text-align: left;
    min-width: 100px;
    padding: 4px;
  }

  table {
    border-top: 1px solid #ebeef5;
    border-left: 1px solid #ebeef5;
    width: 100%;
    // overflow: auto;

    tr {
      height: 44px;
    }

    td {
      min-width: 200px;
      max-width: 400px;
      padding: 4px 8px;
      border-right: 1px solid #ebeef5;
      border-bottom: 1px solid #ebeef5;
    }
  }

  .el-tabs--border-card > .el-tabs__content {
    overflow-x: auto;
  }
}
</style>

<think>好的,我现在需要帮用户解决在Vue3 + Vite + JavaScript项目中使用Element-Plus上传PDF文件,并通过Mammoth实现在线预览的问题。首先,我需要理清楚整个流程需要哪些步骤,然后逐一检查每个步骤的实现方法是否正确,同时确保符合用户的技术栈要求。 首先,用户的技术栈是Vue3、Vite、JavaScript,以及Element-Plus和Mammoth。Element-Plus是UI框架,上传组件应该使用它的Upload组件。Mammoth通常是用来转换.docx文件为HTML的,但用户提到的是PDF,这可能有问题,因为Mammoth不支持PDF。这里可能需要确认用户的需求是否有误,或者是否有其他库替代。但根据用户的问题描述,可能他们实际上是想处理Word文件,而误写成了PDF。或者,可能用户希望将PDF转换为HTML进行预览,这时候可能需要另一个库,比如pdf.js。但问题明确提到Mammoth,所以可能用户的需求有混淆,需要澄清。不过用户可能确实想用Mammoth来处理某种文件,但Mammoth只支持.docx。这时候,可能需要指出这一点,并提供两种解决方案:如果是处理Word文件,使用Mammoth;如果是PDF,则使用pdf.js。 但用户的问题明确提到PDF和Mammoth,这可能有矛盾。因此,在回答时需要指出这一点,并给出两种可能性。假设用户可能混淆了文件类型,我会在回答中先说明Mammoth适用于Word文件,并提供相应的实现步骤,同时建议如果是PDF的话,使用pdf.js的方案,并简要说明。 接下来,分步骤实现。首先,使用Element-Plus上传组件,设置上传动作,限制文件类型为.docx或PDF(根据实际情况)。然后,处理文件上传,使用FileReader读取文件内容。对于Word文件,使用Mammoth转换到HTML,然后将生成的HTML插入到页面中进行预览。如果是PDF,则使用pdf.js来渲染。 但用户的问题中提到的是PDF,所以可能需要调整。但Mammoth不支持PDF,所以这可能是一个矛盾点。因此,在回答中需要指出这一点,并建议如果是Word文件,可以使用Mammoth,而如果是PDF,则需要其他库,比如pdf.js,并分别给出实现方法。 现在,考虑代码部分。在Vue3中,需要安装Element-Plus和Mammoth(或pdf.js)。使用Element-Plus的el-upload组件,配置action属性(可能不需要实际的上传地址,因为是前端处理),设置accept为.docx或.pdf,然后通过before-upload或on-change事件处理文件选择。用FileReader读取文件内容,如果是Word,使用Mammoth的convertToHtml方法,得到结果后渲染到div中。如果是PDF,使用pdf.js的getDocument处理,然后逐页渲染到canvas。 另外需要注意,Mammoth是处理.docx的,而pdf.js处理PDF。所以用户的问题可能存在误解,需要明确这一点。因此,在回答中需要先澄清,然后提供两种情况的解决方案。 最后,生成相关问题,可能涉及不同文件类型的预览,优化上传体验,处理大文件等。</think># Vue3 + Vite + JavaScript实现文件上传预览 ## 一、技术栈选择分析 在Vue3项目中实现文件预览需注意:Mammoth.js专用于.docx文件转换,PDF预览推荐使用PDF.js。Element-Plus的Upload组件支持两种文件类型处理,开发时需注意文件类型匹配[^1]。 ## 二、实现步骤(以Word文件为例) 1. **安装依赖** ```bash npm install element-plus mammoth-js ``` 2. **上传组件配置** ```vue <template> <el-upload action="#" :before-upload="handleFile" accept=".docx,.doc" > <el-button type="primary">上传Word文件</el-button> </el-upload> <div v-html="previewContent"></div> </template> ``` 3. **文件转换逻辑** ```javascript import mammoth from &#39;mammoth-js&#39;; export default { data() { return { previewContent: &#39;&#39; } }, methods: { async handleFile(file) { const reader = new FileReader(); reader.onload = async (e) => { const result = await mammoth.convertToHtml({ arrayBuffer: e.target.result }); this.previewContent = result.value; }; reader.readAsArrayBuffer(file); return false; // 阻止默认上传 } } } ``` ## 三、PDF预览实现方案(使用PDF.js) ```bash npm install pdfjs-dist ``` ```vue <template> <canvas id="pdf-canvas"></canvas> </template> <script> import * as pdfjsLib from &#39;pdfjs-dist/build/pdf&#39;; import &#39;pdfjs-dist/web/pdf_viewer.css&#39;; export default { methods: { async renderPDF(file) { const reader = new FileReader(); reader.onload = async (e) => { const pdf = await pdfjsLib.getDocument(e.target.result).promise; const page = await pdf.getPage(1); const viewport = page.getViewport({ scale: 1.5 }); const canvas = document.getElementById(&#39;pdf-canvas&#39;); canvas.height = viewport.height; canvas.width = viewport.width; page.render({ canvasContext: canvas.getContext(&#39;2d&#39;), viewport }); }; reader.readAsArrayBuffer(file); } } } </script> ``` ## 四、注意事项 1. 文件大小限制建议通过`before-upload`钩子实现 2. 添加加载状态提升用户体验 3. PDF.js需要服务端配置CORS头 4. 使用`v-html`时注意XSS防护
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值