学习目标:

我们在开发后台时肯定避免不了上传图片的功能

例如:

  • 上传图片回显
  • 上传完成 : 预览查看 , 删除等
  • 如果是图片列表,还可能让你拖动图片排序
  • 有的后台项目可能要给图片添加水印,添加标记
  • 有的后台可能要炫酷一点 添加进度条功能

学习内容:

现在我们要完成上面的一系列功能,这里我用到了vue element ui的弹窗组件,预览图片组件,还有axios,axios是二次封装的,你们可以自行根据你们的项目来封装

效果演示:

基于vue element-ui 封装上传图片组件


上传图片组件代码

<!--* @Author: 等风来 3064436257@qq.com* @Date: 2022-11-10 08:42:34* @LastEditTime: 2023-02-17 11:07:40
-->
<template><div class="UploadImg_list"><transition-group name="tag" class="UploadImg_list"><divv-for="(item, index) in imgList":key="item.id":draggable="true"class="UploadImg_item"@dragstart="dragstart(index)"@dragenter="dragenter($event, index)"@dragover="dragover($event, index)"><div class="item_cont"><img :src="item.url" alt="" /><div class="img_mask"><iclass="el-icon-zoom-in"style="margin-right: 10px"@click="lookImg(item)"></i><i class="el-icon-delete" @click="delImg(item)"></i></div></div></div><divv-if="imgList.length < form.total":key="-999999999"class="UploadImg_input"@click="showUploadImg"><i class="el-icon-plus"></i></div></transition-group><el-imageref="preview"class="nones":src="url":preview-src-list="srcList"></el-image><div v-show="is_Ball" class="my_mask"><Ball :percent="ballNum" :run-num="runNum"></Ball></div><!-- 对话框 --><div class="UploadImg"><el-dialogtitle="上传图片配置":close-on-click-modal="false":visible.sync="isUploadImg"width="500px":append-to-body="true"><div><el-form ref="form" :model="form" label-width="70px"><el-form-item v-if="form.total" label="图片总数"><el-input-numberv-model="form.total":min="1":max="maxNum":disabled="maxNumHide"label="图片总数"style="width: 100%"></el-input-number></el-form-item><el-form-item label="图片尺寸"><el-switch v-model="form.size"></el-switch></el-form-item><el-form-item v-if="form.size" label="图片宽度"><el-input-numberv-model="form.width":min="50"label="图片宽度"style="width: 100%"></el-input-number></el-form-item><el-form-item v-if="form.size" label="图片高度"><el-input-numberv-model="form.height":min="50"label="图片高度"style="width: 100%"></el-input-number></el-form-item><el-form-item label="图片类型"><el-checkbox-group v-model="form.imgTypes"><el-checkbox v-for="item in imgType" :key="item" :label="item">{{ item }}</el-checkbox></el-checkbox-group></el-form-item><el-form-item label="是否水印"><el-switch v-model="form.Watermark"></el-switch></el-form-item><el-form-item v-if="form.Watermark" label="水印文字"><el-inputv-model="form.title"maxlength="6"show-word-limit></el-input></el-form-item><el-form-item label="是否多选"><div><el-switch v-model="form.Multiple"></el-switch><span v-if="!form.Multiple" style="margin-left: 10px">当前进度条功能开启</span><span v-else style="margin-left: 10px">当前进度条功能关闭</span></div><p class="MultipleTitle">单选时开启进度条功能,多选则关闭进度条功能</p></el-form-item></el-form></div><span slot="footer" class="dialog-footer UploadImg_btn"><el-button @click="isUploadImg = false">取 消</el-button><el-button type="primary">选择图片<inputv-if="form.Multiple"type="file"accept="image/*"multiple="multiple"@change="addImgs"/><input v-else type="file" accept="image/*" @change="addImgs" /></el-button></span></el-dialog></div></div>
</template><script>import Ball from '../../components/Ball/index.vue'export default {name: 'UploadImg',components: { Ball: Ball },props: {//图片数据lists: {type: Array,default() {return []},},//图片限制个数maxNum: {type: Number,default() {return 5},},//图片限制是否能调整maxNumHide: {type: Boolean,default() {return false},},//图片类型限制imgType: {type: Array,default() {// return ['image/jpeg', 'image/png']return ['image/jpeg']},},//是否限制图片尺寸isSize: {type: Boolean,default() {return false},},//限制图片尺寸大小imgSize: {type: Array,default() {return [400, 400]},},//是否水印isWatermark: {type: Boolean,default() {return false},},//水印文字watermarkTitle: {type: String,default() {return '默认水印'},},// 1 选择图片是否多选// 2 false 时开启进度条功能isMultiple: {type: Boolean,default() {return true},},},data() {return {imgList: this.lists,url: '',srcList: [],// 源对象的下标dragIndex: '',// 目标对象的下标enterIndex: '',timeout: null,isUploadImg: false,form: {total: this.maxNum,size: this.isSize,width: this.imgSize[0],height: this.imgSize[1],title: this.watermarkTitle,Watermark: this.isWatermark,Multiple: this.isMultiple,imgTypes: [...this.imgType],},ballNum: 0,is_Ball: false,runNum: 0,}},watch: {maxNum: {handler: function (a, b) {this.form.total = a},// deep: true,},},methods: {// 显示上传图片对话框showUploadImg() {this.isUploadImg = true},// 上传图片前置验证addImgBefore(fileArr) {return new Promise((resolve, reject) => {let isOk = true// 上传图片数量限制if (this.imgList.length + fileArr.length > this.form.total) {if (this.imgList.length > this.form.total) {const del_nums = this.imgList.length - this.form.totalthis.imgList.splice(this.form.total, del_nums)}this.$message.error(`最多上传 ${this.form.total} 张图片!`)return resolve(false)}// 上传图片类型限制if (this.form.imgTypes.length > 0) {fileArr.forEach((item) => {if (this.form.imgTypes.indexOf(item.type) < 0) {isOk = false}})if (!isOk) {this.$message.error(`上传图片中类型不符合 ${this.form.imgTypes.toString()} 类型 !`)return resolve(false)}}//上传图片判断尺寸if (this.form.size) {fileArr.forEach((fileItem) => {this.limitFileWH(this.form.width,this.form.height,fileItem).then((res) => {if (res) {return resolve(true)} else {return resolve(false)}})})} else {resolve(true)}})},// 上传图片async addImgs(e) {let file = [...e.target.files]let isOk = await this.addImgBefore(file)// 前置验证if (!isOk) {return}if (file.length > 1) {this.runNum = 0.5} else {this.runNum = 3}// 是否开启进度条if (!this.form.Multiple && file.length > 0) {this.is_Ball = true}this.ballNum = 0if (this.form.Watermark) {// 有水印的file.forEach(async (item) => {this.getBase64(item).then((res) => {this.imgToCanvas(res).then((data) => {this.imgList.push({id: new Date().valueOf(),url: data,})this.ballNum = 0this.is_Ball = false})})})} else {// 不加水印的file.forEach(async (item) => {const res = await this.$http.post('admin/api/upload',{file: item,disk: 'public',folder: 'richText',},{headers: {'Content-Type': 'multipart/form-data',},onUploadProgress: (progressEvent) => {let persent =((progressEvent.loaded / progressEvent.total) * 100) | 0 //上传进度百分比this.ballNum = persent},})if (res.code == 200000) {this.imgList.push({id: new Date().valueOf(),url: res.data.url,})this.ballNum = 0this.is_Ball = falsethis.$message.success(res.message)} else {this.$message.error(res.message)this.is_Ball = falsereturn}})}this.isUploadImg = false},// 判断图片是否正方形limitFileWH(E_width, E_height, file) {const _this = thislet imgWidth = ''let imgHight = ''const isSize = new Promise(function (resolve, reject) {const width = E_widthconst height = E_heightconst _URL = window.URL || window.webkitURLconst img = new Image()img.onload = function () {imgWidth = img.widthimgHight = img.heightconst valid = img.width === width && img.height === heightvalid ? resolve() : reject()}img.src = _URL.createObjectURL(file)}).then(() => {return true},() => {_this.$message.warning({message:'上传图片的尺寸应为' +E_width +'*' +E_height +',当前上传图片的尺寸为:' +imgWidth +'*' +imgHight,btn: false,})return false})return isSize},// 转64getBase64(file) {return new Promise(function (resolve, reject) {const reader = new FileReader()let imgResult = ''reader.readAsDataURL(file)reader.onload = function () {imgResult = reader.result}reader.onerror = function (error) {reject(error)}reader.onloadend = function () {resolve(imgResult)}})},// 64转文件dataURLtoFile(dataurl, filename) {var arr = dataurl.split(',')var mime1 = arr[0].split(';')var mime2 = mime1[0].split(':')[1]var bstr = atob(arr[1])var n = bstr.lengthvar u8arr = new Uint8Array(n)while (n--) {u8arr[n] = bstr.charCodeAt(n)}return new File([u8arr], 'names', { type: mime2 })},// 画水印 并上传imgToCanvas(imgSrc) {return new Promise((resolve, reject) => {const _this = thisvar image = document.createElement('img')image.setAttribute('src', imgSrc)const img_Src = ''// 创造画布image.onload = async () => {var canvas = document.createElement('canvas')canvas.width = image.widthcanvas.height = image.heightvar cxt = canvas.getContext('2d')// 将图片绘制上去cxt.drawImage(image, 0, 0) // 第一个参数是图片(不能是src 否则会报错,是src的话需要先new Image(),具体看上个getImgWay方法) 第二、三是图片在画布位置 第四、五是将图片绘制成多大宽高(不写四五就是原图宽高)// 给画布上添加水印文字cxt.font = '24px Georgia'cxt.textAlign = 'left'cxt.textBaseline = 'top'cxt.fillStyle = '#fff'//   cxt.rotate(-10 * Math.PI / 180)cxt.rotate((Math.PI / 180) * 5)if (image.height > image.width) {for (let i = 0; i < image.height / 200; i++) {for (let j = 0; j < image.width / 50; j++) {cxt.fillText(this.form.title, i * 300, j * 100, image.width)}}} else {for (let i = 0; i < image.width / 200; i++) {for (let j = 0; j < image.height; j++) {cxt.fillText(this.form.title, i * 300, j * 100, image.width)}}}var dataurl = canvas.toDataURL('image/png')// 转好的文件 可根据你们的后端接口自行上传const file = this.dataURLtoFile(dataurl)// const formdata = new FormData()// formdata.append('file', file)const res = await this.$http.post('admin/api/upload',{file: file,disk: 'public',folder: 'richText',},{headers: {'Content-Type': 'multipart/form-data',},onUploadProgress: (progressEvent) => {let persent =((progressEvent.loaded / progressEvent.total) * 100) | 0 //上传进度百分比this.ballNum = persent},})// const { data: res } = await $http.post('api/upload', formdata)if (res.code != 200000) {this.$message.error(res.message)this.is_Ball = falseresolve(false)return}resolve(res.data.url)this.$message.success(res.message)}})},// 删除图片delImg(item) {this.$confirm('确定删除该张图片吗?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',}).then(() => {let num = -1this.imgList.forEach((i, index) => {if (item.url == i.url) {num = index}})this.imgList.splice(num, 1)this.$message({type: 'success',message: '删除成功!',})}).catch(() => {})},// 查看图片lookImg(item) {this.srcList = []this.imgList.forEach((i, index) => {this.srcList.push(i.url)})this.url = item.urlthis.$refs.preview.clickHandler()},// 拖动排序dragstart(index) {this.dragIndex = index},// dragenter 和 dragover 事件的默认行为是拒绝接受任何被拖放的元素。// 因此,我们要在这两个拖放事件中使用`preventDefault`来阻止浏览器的默认行为dragenter(e, index) {let indexs = -1e.preventDefault()this.enterIndex = indexif (this.timeout !== null) {clearTimeout(this.timeout)}// 拖拽事件的防抖this.timeout = setTimeout(() => {if (this.dragIndex !== index) {const source = this.imgList[this.dragIndex]this.imgList.splice(this.dragIndex, 1)this.imgList.splice(index, 0, source)// 排序变化后目标对象的索引变成源对象的索引this.dragIndex = index}}, 250)},dragover(e, index) {e.preventDefault()},},}
</script><style lang="scss" scoped>.UploadImg_list {display: flex;flex-wrap: wrap;align-items: center;.UploadImg_item {transition: transform 0.3s;box-sizing: border-box;width: 90px;height: 90px;border: 1px solid #c0ccda;border-radius: 4px;margin-right: 5px;padding: 3px;cursor: move;margin: 5px 0;margin-right: 5px;margin-bottom: 0;i {cursor: pointer !important;}.item_cont {position: relative;box-sizing: border-box;width: 100%;height: 100%;// background-color: red;border-radius: 2px;overflow: hidden;border: 1px solid #c0ccda;transition: all 0.3s;img {transition: all 0.3s;width: 100%;height: auto;}}.img_mask {position: absolute;left: 0;top: 0;width: 100%;height: 100%;background-color: rgba(0, 0, 0, 0.4);display: flex;align-items: center;justify-content: center;color: #fff;font-size: 20px;opacity: 0;}}.UploadImg_input {float: left;position: relative;width: 90px;height: 90px;border: 1px dashed #c0ccda;box-sizing: border-box;border-radius: 4px;display: flex;align-items: center;justify-content: center;font-size: 20px;color: #c0ccda;margin: 5px 0;margin-bottom: 0;cursor: pointer !important;background-color: #fbfdff;}.UploadImg_input:hover {border: 1px dashed #0187fb;color: #0187fb;}.UploadImg_item:hover {box-shadow: 0 2px 12px 0 rgb(1 133 249 / 60%);border: 1px solid hsl(208, 99%, 49%);}.UploadImg_item:hover img {transform: scale(1.4);}.UploadImg_item:hover .img_mask {opacity: 1;}}.nones {display: none;}.tag-enter-active {animation: tagFrames 0.3s linear;}.tag-leave-active {animation: tagFrames 0.3s linear reverse;}@keyframes tagFrames {from {transform: translateY(-30px);opacity: 0;}to {opacity: 1;transform: translateY(0px);}}::v-deep .UploadImg .el-dialog__title {font-weight: 700;}::v-deep .UploadImg .el-form-item__label {font-weight: 700;}::v-deep .UploadImg .el-dialog__body {border: none;}::v-deep .UploadImg .el-dialog__footer {border: none;}::v-deep .UploadImg_btn .el-button span {cursor: pointer !important;}.UploadImg_btn .el-button {width: 90px;height: 36px;font-size: 15px;// line-height: 36px;position: relative;cursor: pointer;input {position: absolute;left: 0;top: 0;opacity: 0;width: 100%;height: 100%;cursor: pointer !important;}}.my_mask {position: fixed;left: 0;top: 0;width: 100vw;height: 100vh;z-index: 99999999999;background-color: rgba(0, 0, 0, 0.6);display: flex;align-items: center;justify-content: center;}.MultipleTitle {margin: 0;color: #f56c6c;font-size: 14px;}
</style>

进度条小球组件代码

<template><div class="pie-wrap"><div:style="{ animationDelay: delay }":class="['pie', { 'pie-all': num == 100 }]"><div class="pie-inner"><p class="percent">{{ num }}%</p><p class="txt">上传中...</p></div></div></div>
</template><script>export default {name: 'Pie',props: {percent: {type: Number,default: 0,},runNum: {type: Number,default: 3,},},data() {return {num: 0,}},computed: {delay() {// 转化为延迟多少秒return `-${this.num}s`},},watch: {percent() {this.startAnimate(this.runNum, this.percent, 5)},},mounted() {this.startAnimate(this.runNum, this.percent, 5)},methods: {// 匀动动画startAnimate(step, limit, speed) {setTimeout(() => {if (this.num < limit) {this.num += stepthis.startAnimate(step, limit, speed)} else {this.num = limit}}, speed)},},}
</script><style lang="scss" scoped>.pie-wrap {width: 100%;height: 200px;text-align: center;.pie {display: inline-block;position: relative;width: 160px;height: 160px;margin-top: 40px;border-radius: 50%;background: #ccc;background-image: linear-gradient(to right, transparent 50%, #4479fd 0);color: transparent;text-align: center;}.pie::before {content: '';position: absolute;top: 0;left: 50%;width: 50%;height: 100%;border-radius: 0 100% 100% 0 / 50%;background-color: inherit;transform-origin: left;animation: spin 50s linear infinite, bg 100s step-end infinite;animation-play-state: paused;animation-delay: inherit;}.pie-all {background: #4479fd;background-image: none;}.pie-all::before {background-color: #4479fd;}@keyframes spin {to {transform: rotate(0.5turn);}}@keyframes bg {50% {background: #4479fd;}}.pie-inner {content: '';position: absolute;top: 50%;left: 50%;width: 90%;height: 90%;border-radius: 50%;background: #fff;transform: translate(-50%, -50%);.percent {margin-top: 38px;margin-bottom: 10px;font-size: 30px;color: #000;}.txt {font-size: 15px;color: #666;margin: 0;}}}
</style>

使用

<template><div style="padding: 50px"><h1>上传图片(查看,删除,预览,拖动排序,水印,单个上传开启进度条)</h1><upload-img:lists="imgList":max-num="5":img-type="['image/png']":is-size="true":img-size="[400, 400]":is-watermark="true":watermark-title="'默认水印'":is-multiple="false"></upload-img><ul><li v-for="item in imgList" :key="item.id">{{ item.id }} -- {{ item.url }}</li></ul></div>
</template><script>import uploadImg from '../../components/uploadImg/index.vue'export default {components: { 'upload-img': uploadImg },data() {return {imgList: [{id: 12,url: '*****',},],}},}
</script><style></style>

注意:

imgList格式一定要注意 id为唯一值 你们可以取当前时间戳 或者 后端返回的图片id

使用

  1. lists 是图片数组数据 格式一定要对

  2. max-num-hide 对话框 是否可以修改图片个数

  3. max-num 是最多图片个数 不是选中图片个数

  4. img-type 是图片类型 是数组 这里我没做处理 直接用的['image/jpeg', 'image/png']这种格式,你们想要修改,请自行修改

  5. is-size 是上传图片前是否判断图片尺寸 根据 img-size 来判断 如果is-size 为false 那img-size 配置不生效

  6. img-size 是限制图片尺寸 为数组 第一项为图片宽度 第二项为图片高度

  7. is-watermark 是 是否开启水印功能

  8. watermark-title 是水印的文字 如果 is-watermark 为fasle啧该配置也无效

  9. is-multiple 是选择图片是否为多选 true 为多选 ,false 为单选 单选是则开启上传进度条功能

这个组件并没有封装上传地址什么的 自行去源码修改 可根据你们后端的接口方式自行上传 很简单 你们肯定会


学习总结:

功能也算挺完善的一个上传图片组件,已知的就差裁剪功能了,如果有bug请联系我,谢谢,随便写着玩的

基于vue element-ui 封装上传图片组件 功能:上传,删除,预览,上传图片水印,拖拽排序,上传进度条等相关推荐

  1. 闲云旅游网03(基于vue+element ui)登录注册功能开发

    登录功能 项目GitHub地址:https://github.com/q2419068625/xianyun 新建登录注册页的布局 新建了登录的表单 绑定了数据到data rules表单的验证 提交数 ...

  2. 基于vue(element ui) + ssm + shiro 的权限框架

    zhcc 基于vue(element ui) + ssm + shiro 的权限框架 引言 心声 现在的Java世界,各种资源很丰富,不得不说,从分布式,服务化,orm,再到前端控制,权限等等玲琅满目 ...

  3. 基于Vue+Element UI+Node+MongoDB的医院门诊预约挂号系统

    目录 概述 3 系统目标 3 需求分析 3 功能需求 3 非功能需求 4 设计 4 数据库设计 4 数据库说明 4 数据结构 4 接口设计 5 登录 5 注册.添加信息 6 查询信息 6 查询全部病人 ...

  4. 微信小程序上传图片组件,多选+单选+预览+删除

    微信小程序上传图片+预览+删除 组件代码 HTML <view class="uploadImg-wrap"><view class="upload-f ...

  5. vue+element UI的 table组件实现日历

    有现成的日历插件但是不符合需求,所以项目中使用vue+element 的表格组件自己实现一个日历组件 核心js部分:此部分为计算的当月的日期且包含是否可选,是否节假日等等可操作的标记,这部分基本是实现 ...

  6. 闲云旅游网01(基于vue+element ui)

    1.初始化项目 使用官网提供的脚手架工具 create-nuxt-app,创建一个nuxtjs项目. npx create-nuxt-app xianyun 需要等待片刻安装依赖的下载,下载完成后可以 ...

  7. Vue图片上传删除预览操作

    样式是通过element-ui实现的,使用的时候要先导入所需组件 html <!-- action 表示图片要上传到的后台API地址 (1.9接口) --> <!-- on-prev ...

  8. html元素拖拽预览图,HTML5拖拽上传图片预览

    1.文件API:(File API) file类型的的表单控件选择的每一个文件都是一个file对象,而FileList对象则是这些file对象的集合列表,代表所选择的所有文件.file对象继承于Blo ...

  9. 闲云旅游网02(基于vue+element ui)

    首页开发 项目GitHub地址:https://github.com/q2419068625/xianyun 新建公共组件 在components中新建应用统一的头部组件和页脚组件. 在默认布局中la ...

最新文章

  1. 【自动驾驶】17. pitch yaw roll是什么
  2. 配置php.ini文件,关闭错误提示,打开错误日志,设置错误日志路径(亲测)
  3. unlegal android,百度地图定位 Cordova 插件 cordova-plugin-baidumaplocation
  4. flink启动yarn-session报错javax/ws/rs/ext/MessageBodyReader
  5. windows录屏_电脑自带录屏软件怎么打开?详细操作教程
  6. 【Flink】运行Flink 1.6.2 程序偶然报错 Premature end of GET request
  7. 本博已停用,现在的博客是www.mutousay.com
  8. php if条件循环语句,PHP:IF语句可以单独运行,但不能在WHILE循环内运行
  9. python 数据处理(以Pandas为主)
  10. java实现modbus rtu协议与 modscan等工具(4)rtu转tcp
  11. 计算机控制系统第二章答案,计算机控制技术(第2版)部分课后题答案
  12. java 自然常数e中出现的连续的第一个10个数字组成的质数_自然常数-常数e的来历e在很多数学公式中出现的频率比较高今天做导数题时看到 爱问知识人...
  13. 华为鸿蒙如何添加桌面小组件,万能小组件添加至桌面怎么弄?桌面添加应用方法图文详解...
  14. 在线词云工具Tagxedo的使用
  15. 迅雷 极速版 1.0.35.366
  16. c语言中fgetc函数的介绍
  17. NODEJS day_04(5.24)Express-2
  18. qrcode(3):jquery-qrcode生成二维码
  19. ai人工智能可以干什么_什么是情感AI,为什么要关心
  20. JavaScript 实例:当当网 首页选项卡切换效果

热门文章

  1. 计算机科学杂志社联系电话,《计算机科学》杂志社编辑部.doc
  2. iwconfig连接WiFi 热点上网
  3. 拉伯证券|北向资金1月净买入超1400亿,啥信号?
  4. EPUB是什么格式?PDF如何转换成EPUB格式?
  5. 微信小程序中Map地图组件的使用
  6. 可逆网络 ICLR 2017 : Density Estimation Using Real NVP
  7. 互斥的数(codevs1553)
  8. matlab加速迭代法方程求根,【源码】迭代法求根的matlab算法
  9. 深度学习算法优化系列十八 | TensorRT Mnist数字识别使用示例
  10. 系统级dialog与软键盘的冲突处理