前言:使用的是若依的框架+element ui+vue2封装的。如果有不对的地方欢迎指出。后台管理使用,文件需要上传。回显列表,详情也需要回显+预览
// 开始封装组件:封装在 src/components/FileUpload/index.vue中
<template><div class="upload-file"><el-uploadmultiplename="multipartFile":action="uploadFileUrl":data="{ 上传时附带的额外参数 }":limit="limit":file-list="fileList":before-upload="handleBeforeUpload":on-exceed="handleExceed":on-error="handleUploadError":on-success="handleUploadSuccess":show-file-list="false":headers="headers"class="upload-file-uploader"ref="fileUpload"><!-- 上传按钮 --><el-button size="mini" type="primary" plain v-if="!disabled">选取文件</el-button><!-- 上传提示 --><div class="el-upload__tip" slot="tip" v-if="showTip && !disabled">请上传<template v-if="fileSize">大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b></template><template v-if="fileType">格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b></template>的文件</div></el-upload><!-- 文件列表,我们功能需要 删除文件、下载文件、预览文件功能 --><transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul" style="min-width: 300px"><li class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList" :key="file.fileId"><el-link class="link" :href="`${baseUrl}${file.filePath}`" :underline="false" target="_blank"><span class="el-icon-document" style="padidng-left: 10px">{{ getFileName(file.fileName) }}</span></el-link><div class="controls"><div class="ele-upload-list__item-content-action" style="width: 50px; text-align: center"><el-link :underline="false" @click="handleDelete(index)" type="danger" v-if="!disabled">删除</el-link></div><div class="ele-upload-list__item-content-action" style="width: 50px; text-align: center"><el-link :underline="false" @click="handlePreview(file)" type="danger">预览</el-link></div><div class="ele-upload-list__item-content-action" style="width: 50px; text-align: center"><el-link :underline="false" @click="handleDownload(file)" type="danger">下载</el-link></div></div></li></transition-group><!-- 文件预览功能,点击文件列表后的预览按钮,需要预览文档,pdf,excel,照片,视频,音频。其他没有封装,所以就不支持 --><el-dialog title="文件预览" :visible.sync="preview.open" append-to-body :before-close="previewCancel"><vue-office-docx v-if="fileSuffix == 'docx'" :src="preview.url" style="height: 100vh" /><vue-office-excel v-else-if="fileSuffix == 'xlsx'" :src="preview.url" style="width: auto; height: 100vh" /><vue-office-pdf v-else-if="fileSuffix == 'pdf'" :src="preview.url" style="height: 100vh" /><div style="text-align: center" v-else-if="fileSuffix == 'img'"><el-image style="width: 500px" :src="preview.url" fit="fill" :preview-src-list="[preview.url]"></el-image></div><div style="text-align: center" v-else-if="fileSuffix == 'mp3'"><audio controls loop ref="myAudio" autoplay class="my-audio"><source :src="preview.url" /></audio></div><div style="text-align: center" v-else-if="fileSuffix == 'mp4'"><video-app :src="preview.url" :second="1"></video-app></div><div style="text-align: center" v-else>暂不支持该文件预览,请下载预览</div></el-dialog></div>
</template><script>
import { getToken } from "@/utils/auth";
import { 查看和下载的接口,后端给的 } from "";//引入VueOfficeDocx组件,需要npm安装
import VueOfficeDocx from "@vue-office/docx";
import "@vue-office/docx/lib/index.css";//引入VueOfficeExcel组件,需要npm安装
import VueOfficeExcel from "@vue-office/excel";
import "@vue-office/excel/lib/index.css";//引入VueOfficePdf组件,需要npm安装
import VueOfficePdf from "@vue-office/pdf";import VideoApp from "./video.vue"; // 这个是我封装的视频预览的组件export default {name: "FileUpload",props: {// 数量限制limit: {type: Number,default: 5,},// 大小限制(MB)fileSize: {type: Number,default: 20,},// 文件类型, 例如['png', 'jpg', 'jpeg']fileType: {type: Array,default: () => ["docx", "pptx", "pdf"],},// 是否显示提示isShowTip: {type: Boolean,default: true,},formFileList: {type: Array,default: () => [],},disabled: {type: Boolean,default: false,},// 培训记录附件的类型busiType: {type: String,},},components: {VueOfficeDocx,VueOfficeExcel,VueOfficePdf,VideoApp,},data() {return {number: 0,uploadList: [],baseUrl: // 地址 ,uploadFileUrl: , // 上传文件服务器地址headers: {Authorization: "Bearer " + getToken(),},fileList: [],lookFile: false,url: "",// 文件预览preview: {open: false,url: "",},fileSuffix: "",};},watch: {// 编辑和详情的回显fileListformFileList: {handler(val) {if (val !== undefined) {this.fileList = val;}if (val == null) {this.fileList = [];return;}},deep: true,immediate: true,},},computed: {// 是否显示提示showTip() {return this.isShowTip && (this.fileType || this.fileSize);},},methods: {// 上传前校检格式和大小handleBeforeUpload(file) {// 校检文件类型if (this.fileType) {const fileName = file.name.split(".");const fileExt = fileName[fileName.length - 1];const isTypeOk = this.fileType.length ? this.fileType.indexOf(fileExt) >= 0 : [];if (!isTypeOk) {this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);return false;}}// 校检文件大小if (this.fileSize) {const isLt = file.size / 1024 / 1024 < this.fileSize;if (!isLt) {this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`);return false;}}this.$modal.loading("正在上传文件,请稍候...");this.number++;return true;},// 文件个数超出handleExceed() {this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);},// 上传失败handleUploadError(err) {this.number--;this.$modal.msgError("上传文件失败,请重试");this.$modal.closeLoading();},// 上传成功回调handleUploadSuccess(res, file) {if (res.code === 200) {this.uploadList.push(res.data);this.$modal.closeLoading();this.uploadedSuccessfully();} else {this.number--;this.$modal.closeLoading();this.$modal.msgError(res.msg);this.$refs.fileUpload.handleRemove(file);this.uploadedSuccessfully();}},// 删除文件handleDelete(index) {this.fileList.splice(index, 1);this.$emit("input", this.listToString(this.fileList));},// 上传结束处理uploadedSuccessfully() {if (this.number > 0 && this.uploadList.length === this.number) {this.fileList = this.fileList.concat(this.uploadList);this.uploadList = [];this.number = 0;this.$emit("input", this.listToString(this.fileList));this.$modal.closeLoading();this.$emit("fileUploadSuccess", this.fileList);}},// 获取文件名称getFileName(name) {if (name?.lastIndex/Of("/") > -1) {return name.slice(name.lastIndexOf("/") + 1);} else {return name;}},// 对象转成指定字符串分隔listToString(list, separator) {let strs = "";separator = separator || ",";for (let i in list) {strs += list[i].url + separator;}return strs != "" ? strs.substr(0, strs.length - 1) : "";},resetFileList() {this.fileList = [];},// 下载handleDownload(file) {downloadFile(file.fileId).then((res) => {this.exportFunction(res, file.fileName, file.fileType);});},exportFunction(response, name, type) {const link = document.createElement("a");const blob = new Blob([response], { type });link.style.display = "none";link.href = URL.createObjectURL(blob);link.setAttribute("download", name, type);document.body.appendChild(link);link.click();document.body.removeChild(link);},// 预览handlePreview(file) {this.preview.url = "";if (file.fileType.indexOf("image") !== -1) {this.fileSuffix = "img";} else {this.fileSuffix = file.suffix;}showFileURL(file.fileId).then((res) => {this.preview.url = defaultSettings.minioUrl + res;if (file.suffix == "mp3") {this.$nextTick((res) => {this.$refs.myAudio.load();this.$refs.myAudio.play();});}this.preview.open = true;});},previewCancel() {this.preview.open = false;this.preview.url = "";if (this.fileSuffix == "mp3") {this.$refs.myAudio.pause();}},},
};
</script><style scoped lang="scss">
::v-deep .el-button--primary.is-plain {color: var(--select-selected-color);background: var(--table-content-bColor);border-color: var(--search-back);
}
::v-deep .el-button--primary.is-plain:hover,
::v-deep .el-button--primary.is-plain:focus {border-color: var(--select-selected-color);background-color: var(--select-selected-color);color: #fff !important;
}.upload-file-uploader {margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {border: 1px solid #e4e7ed;line-height: 2;margin-bottom: 10px;position: relative;
}
.upload-file-list .ele-upload-list__item-content {display: flex;color: inherit;.link {flex: 1;display: block;margin-left: 2px;::v-deep .el-icon-document {width: 310px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}}.controls {display: flex;}
}
.ele-upload-list__item-content-action .el-link {margin-right: 10px;
}
::v-deep {.x-spreadsheet-table {width: auto !important;}
}
.my-audio {width: 100%;
}
</style>
video组件封装:
<template><div class="m-video" :class="{'u-video-hover': !hidden}" :style="`width: ${width}px; height: ${height}px;`"><videoref="veo":style="`object-fit: ${zoom};`":src="src":poster="veoPoster":width="width":height="height":autoplay="autoplay":controls="!originPlay&&controls":loop="loop":muted="autoplay || muted":preload="preload"crossorigin="anonymous"@loadeddata="poster ? () => false : getPoster()"@pause="showPlay ? onPause() : () => false"@playing="showPlay ? onPlaying() : () => false"@click.prevent.once="onPlay"v-bind="$attrs">您的浏览器不支持video标签。</video><svg v-show="originPlay || showPlay" class="u-play" :class="{'hidden': hidden}" :style="`width: ${playWidth}px; height: ${playWidth}px;`" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.75 6.75C4.75 5.64543 5.64543 4.75 6.75 4.75H17.25C18.3546 4.75 19.25 5.64543 19.25 6.75V17.25C19.25 18.3546 18.3546 19.25 17.25 19.25H6.75C5.64543 19.25 4.75 18.3546 4.75 17.25V6.75Z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.25 12L9.75 8.75V15.25L15.25 12Z"></path></svg></div>
</template>
<script>
export default {name: 'Video',props: {src: { // 视频文件url,必传,支持网络地址 https 和相对地址 require('@/assets/files/Bao.mp4')type: String,required: true,default: ''},poster: { // 视频封面url,支持网络地址 https 和相对地址 require('@/assets/images/Bao.jpg')type: String,default: ''},second: { // 在未设置封面时,自动截取视频第 second 秒对应帧作为视频封面type: Number,default: 0.5},width: { // 视频播放器宽度type: Number,default: 800},height: { // 视频播放器高度type: Number,default: 450},autoplay: { // 视频就绪后是否马上播放type: Boolean,default: false},controls: { // 是否向用户显示控件,比如进度条,全屏type: Boolean,default: true},loop: { // 视频播放完成后,是否循环播放type: Boolean,default: false},muted: { // 是否静音type: Boolean,default: false},preload: { // 是否在页面加载后载入视频,如果设置了autoplay属性,则preload将被忽略;type: String,default: 'auto' // auto:一旦页面加载,则开始加载视频; metadata:当页面加载后仅加载视频的元数据 none:页面加载后不应加载视频},showPlay: { // 播放暂停时是否显示播放器中间的暂停图标type: Boolean,default: true},playWidth: { // 中间播放暂停按钮的边长type: Number,default: 96},zoom: { // video的poster默认图片和视频内容缩放规则type: String,default: 'contain' // none:(默认)保存原有内容,不进行缩放; fill:不保持原有比例,内容拉伸填充整个内容容器; contain:保存原有比例,内容以包含方式缩放; cover:保存原有比例,内容以覆盖方式缩放}},data () {return {veoPoster: this.poster,originPlay: true,hidden: false}},mounted () {if (this.autoplay) {this.hidden = truethis.originPlay = false}/*自定义设置播放速度,经测试:在vue2中需设置:this.$refs.veo.playbackRate = 2在vue3中需设置:veo.value.defaultPlaybackRate = 2*/// this.$refs.veo.playbackRate = 2},methods: {/*loadeddata 事件在媒体当前播放位置的视频帧(通常是第一帧)加载完成后触发preload为none时不会触发*/getPoster () { // 在未设置封面时,自动获取视频0.5s对应帧作为视频封面// 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图this.$refs.veo.currentTime = this.second// 创建canvas元素const canvas = document.createElement('canvas')const ctx = canvas.getContext('2d')// canvas画图canvas.width = this.$refs.veo.videoWidthcanvas.height = this.$refs.veo.videoHeightctx.drawImage(this.$refs.veo, 0, 0, canvas.width, canvas.height)// 把canvas转成base64编码格式this.veoPoster = canvas.toDataURL('image/png')},onPlay () {if (this.originPlay) {this.$refs.veo.currentTime = 0this.originPlay = false}if (this.autoplay) {this.$refs.veo.pause()} else {this.hidden = truethis.$refs.veo.play()}},onPause () {this.hidden = false},onPlaying () {this.hidden = true}}
}
</script>
<style lang="scss" scoped>
* {box-sizing: border-box;margin: 0;padding: 0;
}
.m-video {display: inline-block;position: relative;background: #000;cursor: pointer;.u-play {position: absolute;top: 0;right: 0;bottom: 0;left: 0;margin: auto;fill: none;color: #FFF;pointer-events: none;opacity: 0.7;transition: opacity .3s;path {stroke: #FFF;}}.hidden {opacity: 0;}
}
.u-video-hover {&:hover {.u-play {opacity: 0.9;}}
}
</style>
使用组件:
import FileUpload= from "@/components/FileUpload=";
<el-form-item label="文件上传"><file-uploadref="fileResetRef"@fileUploadSuccess="fileUploadSuccessHandle":formFileList="form.files" // 回显的数据文件列表:disabled="isReadonly" // 区分编辑还是查看:file-type="[ // 支持的类型'png','jpg','docx','xlsx','pptx','pdf','mp3','mp4','zip',]"></file-upload>
</el-form-item>
// 文件上传成功的展示
fileUploadSuccessHandle1(fileList) {this.form.files = fileList;
},
上传组件效果图:
上传的文件列表: