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>