项目使用vue+element-ui,实现了表单多图上传图片,上传视频,以及表单图片回显,视频回显,表格渲染图片等功能
效果图
上传后
图片可回显,视频可播放,,这时候全部缓存在页面,并没有提交到后端服务器,只要到用户提交的那一步才确定上传,减低不必要的服务器开支
图片上传
前端缓存base64方便回显,以及后台上传,视频上传则使用file类型去上传(base64对视频编码会导致请求参数过长)
<!-- 描述:图片上传, 基于 element-ui 组件 --> <template> <div> <div class="upload"> <div class="img_mode" v-for="(item, index) in path_list" :key="index" :style="'height:' + height + ';width:' + width" @mouseenter.stop="over(index)" @mouseleave="out" > <img :src="https://www.atool.online/article/item.path" /> <transition name="el-fade-in-linear"> <div v-show="curIndex == index" class="transition-box"> <div class="mask"> <i class="el-icon-delete" @click="remove(index)"></i> <i class="el-icon-zoom-in" @click="enlarge(index)" v-if="bigBox" ></i> </div> </div> </transition> </div> <div class="select_mode" :style="'height:' + height + ';width:' + width" v-if="isLimit" v-loading="load" > <input type="file" @change="change($event)" ref="file" :multiple="isMultiple" /> <img :src="https://www.atool.online/article/selectImgPath || selectImg" :width="selectImgWidth || '50%'" /> </div> </div> <el-dialog :visible.sync="isShow" center> <div class="big_img_mode"> <img :src="https://www.atool.online/article/bigImg" /> </div> </el-dialog> </div> </template>
<script> //这里是图片上传时候的图标,可以自行替换 import selectImg from "../../assets/img/selectedImg.png"; export default { props: { height: String, width: String, // 组件预览图片大小 selectImgPath: String, // 未选择图片时显示的图片 selectImgWidth: String, bigBox: Boolean, // 是否显示图片放大按钮 /* 多图上传 */ isMultiple: Boolean, // 是否可多选图片 limit_size: Number, // 限制上传数量 quality: Number, // 图片压缩率 limit: Number, // 图片超过 (limit * 1024)kb 将进行压缩 isCompress: Boolean, // 是否开启图片压缩 /* 单图上传 */ isChangeUpload: false, // 是否选择文件后触发上传 action: "", // 单图上传路径, param: "", // 上传的参数名 data: Object, // 单图上传附带的参数 success: Function, // 单图上传成功后的回调函数 }, data() { return { selectImg, path_list: [], curIndex: -1, bigImg: "", isShow: false, isLimit: true, load: false, }; }, watch: { path_list() { if (this.path_list.length >= this.limit_size) { this.isLimit = false; } else { this.isLimit = true; } this.load = false; this.curIndex = -1; }, }, mounted() {}, methods: { // 鼠标移入 over(index) { this.curIndex = index; }, // 鼠标移出 out() { this.curIndex = -1; }, // 选择图片 change() { this.load = true; var That = this; let fileList = this.$refs.file.files; if (!fileList.length) { this.load = false; return; } for (var i = 0; i < fileList.length; i++) { var file_temp = fileList[i]; let name = file_temp.name.toLowerCase(); if (!/\.(jpg|jpeg|image|img|png|bmp|)$/.test(name)) { this.$message.warning("请上传图片"); this.clean(); } let reader = new FileReader(); //html5读文件 reader.fileName = file_temp.name; reader.readAsDataURL(file_temp); reader.onload = (data) => { //读取完毕后调用接口 // 图片压缩 this.canvasDataURL( data.currentTarget.result, { fileSize: data.total, quality: this.quality }, (baseCode) => { if (this.isChangeUpload) { this.changeUpload(baseCode, reader.fileName); } else { this.path_list.push({ path: baseCode, fileName: reader.fileName, }); } } ); }; } this.$emit("change", this.path_list); }, // 移除图片 remove(index) { this.path_list.splice(index, 1); }, //清空图片 clean() { this.path_list = []; }, //后台添加图片 addPathList(urls) { console.log(urls); let arr = urls.split(","); if (arr.length == 1) { let obj = { path: this.$common.getUrl() + arr[0] }; this.path_list.splice(0, 1, obj); } else { arr.forEach((item, index) => { let obj2 = { path: this.$common.getUrl() + item }; this.path_list.splice(index, 1, obj2); }); } }, //后台添加图片 addUrlPathList(urls) { console.log(urls); let arr = urls.split(";"); console.log("数组" + arr); if (arr.length == 1) { let obj = { path: this.$common.getUrl() + arr[0] }; this.path_list.splice(0, 1, obj); } else { arr.forEach((item, index) => { let obj2 = { path: this.$common.getUrl() + item }; this.path_list.splice(index, 1, obj2); }); } }, //重新图片 clearImgs() { this.path_list = []; }, // 放大图片 enlarge(index) { this.isShow = true; this.bigImg = this.path_list[index].path; }, // 单图上传 changeUpload(baseCode, fileName) { let formData = new FormData(); formData.append(this.param, baseCode); formData.append("fileName", fileName); for (var item in this.data) { formData.append(item, this.data[item]); } this.$axios .post(this.action, formData) .then((response) => { if (response.data.message == "succ") { this.success(); // 上传成功后的回调函数 } this.load = false; }) .catch((e) => { console.log(e); this.load = false; }); }, /** * @uploadPath 上传的路径 * @path_list base64 集合, 为空则调用 this.path_list * @callback 上传成功后的回调函数, 返回上传成功后的路径 */ upload(uploadPath, path_list, callback) { var formData = new FormData(); if (!path_list) { this.path_list.forEach((item) => { formData.append( "files", this.convertBase64UrlToBlob(item.path), item.fileName ); }); } else { path_list.forEach((item) => { formData.append( "files", this.convertBase64UrlToBlob(item.path), item.fileName ); }); } let headers = { headers: { "Content-Type": "multipart/form-data" } }; this.$axios .post(uploadPath, formData, headers) .then((response) => { if (response.data.message == "succ") { // 回调函数返回上传成功后的路径 callback("succ", response.data.result); } else { this.$message.error("文件上传失败"); callback("error"); } }) .catch((error) => { if (error == "timeout") { this.$message.error("文件上传超时"); } this.$message.error("文件上传异常"); console.log(error); callback("error"); }); }, getName() { return this.path_list.fileName; }, // 获取base64 集合 getPaths() { let paths = []; if (this.path_list.length == 1) { return this.path_list[0].path; } else if (this.path_list.length == 0) { return ""; } else { for (var i = 0; i < this.path_list.length; i++) { paths.push(this.path_list[i].path); // if(i == 0){ // paths = this.path_list[i].path // }else{ // arr.push(this.path_list[i].path) // } } } return paths; }, photoCompress() {}, // 图片压缩 canvasDataURL(path, obj, callback) { var suffix = path.match(/\/(\S*);/)[1]; var img = new Image(); img.src = path; img.onload = function () { var that = this; // 默认按比例压缩 var w = that.width, h = that.height, scale = w / h; w = obj.width || w; h = obj.height || w / scale; var quality = 0.7; // 默认图片质量为0.7 this.limit = this.limit ? this.limit : 1; // 默认为超过1M 进行压缩 //生成canvas var canvas = document.createElement("canvas"); var ctx = canvas.getContext("2d"); // 创建属性节点 var anw = document.createAttribute("width"); anw.nodeValue = w; var anh = document.createAttribute("height"); anh.nodeValue = h; canvas.setAttributeNode(anw); canvas.setAttributeNode(anh); ctx.drawImage(that, 0, 0, w, h); // 图像质量 if (obj.quality && obj.quality <= 1 && obj.quality > 0) quality = obj.quality; var base64 = ""; if (this.isCompress) { // quality值越小,所绘制出的图像越模糊 base64 = obj.fileSize > this.limit * 1024 ? canvas.toDataURL("image" + suffix, quality) : canvas.toDataURL("image" + suffix); } else { base64 = canvas.toDataURL("image/" + suffix, quality); } // 回调函数返回base64的值 callback(base64); }; }, // base64 转 blob 对象 convertBase64UrlToBlob(urlData) { //去掉url的头,并转换为byte var bytes = window.atob(urlData.split(",")[1]); //处理异常,将ascii码小于0的转换为大于0 var ab = new ArrayBuffer(bytes.length); var ia = new Uint8Array(ab); for (var i = 0; i < bytes.length; i++) { ia[i] = bytes.charCodeAt(i); } return new Blob([ab], { type: "image/png" }); }, }, }; </script>
<style scoped="upload"> .upload { width: 100%; overflow: hidden; display: flex; flex-wrap: wrap; padding: 10px 4px; box-sizing: border-box; } .select_mode { width: 120px; height: 120px; display: flex; justify-content: center; align-items: center; /* border: 1px dashed #cfcfcf; */ border-radius: 10px; position: relative; box-sizing: border-box; /* background-color: #fcfbfe; */ } .select_mode:hover { /* border: 1px dashed #00A2E9; */ } .select_mode img { width: 100%; position: absolute; z-index: 1; cursor: pointer; } .select_mode input[type="file"] { width: 100%; height: 100%; opacity: 0; cursor: pointer; z-index: 2; } .img_mode { margin-right: 10px; margin-bottom: 10px; box-shadow: 0px 1px 5px #cccccc; position: relative; } .mask { background: rgb(0, 0, 0, 0.5); height: 100%; width: 100%; position: absolute; top: 0px; left: 0px; display: flex; justify-content: center; align-items: center; } .img_mode, .img_mode img { height: 120px; max-width: 200px; } .mask i { font-family: element-icons !important; color: #ffffff; font-size: 25px; margin: 0px 8px; cursor: pointer; } .big_img_mode { text-align: center; width: 100%; height: 100%; max-height: 400px; } .big_img_mode img { max-width: 400px; max-height: 400px; } </style>
使用方法:
引入上面的模块,然后在 components里面注册,即可用标签使用
import Upload from "../../../components/utils/Upload.vue"; components: { editor: editor, Upload },
<el-form-item label="轮播图:" prop="img"> //这里面的参数在上面的模块里面去看,ref就是你绑定的对象 <upload ref="addBanner" :limit_size="4" :isCompress="true" :bigBox="true" ></upload> </el-form-item>
上传之前获取base64图片编码,存到表单中,然后直接提交给后台
this.addForm.img = this.$refs.addBanner.getPaths();
注意:这里上传后会是一段base64字符串,如果是多图,会是一段字符串数组,后端可以直接用jsonArray接收
视频上传
//表单 <el-form-item label="视频:" prop="video"> <video class="video" controls v-if="videoShow" :src="https://www.atool.online/article/videoShow" /> <input ref="videoFile" @change="fileChange($event)" type="file" id="video_file" accept="video/*" /> </el-form-item>
//方法 fileChange(e) { var files = e.target.files || e.dataTransfer.files; if (!files.length) return; let name = files[0].name.toLowerCase(); if ( !/\.(avi|wmv|mpeg|mp4|mov|mkv|flv|f4v|m4v|rmvb|rm|3gp|dat|ts|mts|vob)$/.test( name ) ) { this.$message.warning("请上传视频"); return; } if (files[0].size > 1024 * 1024 * 20) { this.$message.warning("视频大小不能大于20M"); return; } //这里是file文件 this.addForm.video = files[0]; var reader = new FileReader(); reader.readAsDataURL(files[0]); reader.onload = () => { //这里是一段base64,用于视频回显用 this.videoShow = reader.result; }; },
表格渲染相关
表格内多图渲染
这段代码的意思是渲染当前行img字段的列表,遍历生成img图片标签
因为img字段返回的是一个数组,不能直接渲染,所以加上一个JSON.parse解析一下
另外加了一个判断最多渲染不超过三张,防止界面过长超出
<el-table-column prop="name" label="轮播图" align="center" width="300px"> <template slot-scope="scope"> <el-image v-for="(item,index) in JSON.parse(scope.row.img)" :src="https://www.atool.online/article/item" v-if="index<3" style="width: 30%;height: 100%;margin-right: 5px" ></el-image> </template> </el-table-column>
以上为个人经验,希望能给大家一个参考,也希望大家多多支持阿兔在线工具。