移动端 element + vue 二次封装上传图片/视频的组件

功能:支持同时上传图片和视频,或者只上传图片、只上传视频,可以预览、删除

 

 

 

项目中使用:

1、图片和视频都支持

<el-form-item label="上传影像" class="ml30">
    <e-upload-img-video
      v-model="fileList"
      :limit="6"
      accept="video/mp4,video/ogg,video/webm,image/png,image/jpg,image/gif,image/jpeg"
      :acceptData="acceptData"
      tip="(注:最多上传6个文件,大小不超过20M,仅支持 mp4, ogg, webm, png, jpg, gif, jpeg 格式)"
      @change="onFileChange"
    >
    </e-upload-img-video>
 </el-form-item>

data() {
return {
       acceptData: {
         // 附件格式
         accept: '.mp4,.ogg,.webm,.png,.jpg,.gif,.jpeg',
         maxsize: 1024 * 1024 * 20,
         maxsize_text: '20M',
         videoTypeList: ['video/mp4', 'video/ogg', 'video/webm'],
         imageTypeList: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif']
       }
   }
}
   onFileChange(fileList) {
      this.form.fileList = fileList
   }


注意:在编辑页面,回显图片的时候,要调用一下initFileList方法,不然之前新增时上传的图片不会显示哦:
   this.image = JSON.parse(res.picture)
   this.$nextTick(() => {
      this.$refs.picture.initFileList(res.picture)
   }

 2、仅支持图片

<e-upload-img-video
   v-model="imgList"
  :limit="16"
  :onlyImage="true"
   accept="image/png,image/jpg,image/gif,image/jpeg"
   :acceptData="imgageData"
   tip="(注:最多上传16个文件,大小不超过50M,仅支持 png, jpg, gif, jpeg 格式)"
   @change="onImageChange"
>
</e-upload-img-video>

data() {
  return {
     imgList: [],
     imgageData:{
        accept: '.png,.jpg,.gif,.jpeg',
        maxsize: 1024 * 1024 * 50,
        maxsize_text: '50M',
        imageTypeList: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif']
      }
  }
},

methods: {
   onImageChange(fileList) {
      this.form.imgList= fileList
      this.imgList = fileList
   }
}

 

注册组件 src/components/index.js

import EUploadImgVideo from './EUploadImgVideo'
 
export default {
install(Vue) {
Vue.component('EUploadImgVideo', EUploadImgVideo)
}
}

 

main.js引入自定义组件:

import Components from '@/components/index'
Vue.use(Components)

 

二次封装好的上传图片的组件 EUploadImgVideo.vue:

  1 <template>
  2   <!-- 支持同时上传图片和视频,或者只上传图片、只上传视频,可以预览、删除 -->
  3   <div class="any-upload-img-video">
  4     <el-upload
  5       :action="uploadApi()"
  6       list-type="picture-card"
  7       :data="acceptData"
  8       :limit="limit"
  9       :disabled="disabled"
 10       :multiple="multiple"
 11       :accept="accept"
 12       :file-list="fileList"
 13       :on-exceed="handleExceed"
 14       :on-change="(file, fileList) => handleChange(file, fileList)"
 15       :on-success="(response, file, fileList) => handleSuccess(response, file, fileList)"
 16       :before-upload="file => beforeUpload(file)"
 17     >
 18       <i slot="default" class="el-icon-camera"></i>
 19       <div slot="file" slot-scope="{ file }" class="custom-file-list">
 20         <!-- 删除 -->
 21         <i class="el-icon-close" v-if="!disabled" @click="handleRemove(file)"></i>
 22 
 23         <!-- 视频 -->
 24         <video
 25           class="el-upload-list__item-thumbnail"
 26           :src="file.url"
 27           alt=""
 28           @click="handlePreview(file, 'video')"
 29           v-if="
 30             ['mp4', 'ogg', 'webm', 'avi', 'wma', 'mpeg', 'mov', 'mlv'].indexOf(
 31               file.name.substring(file.name.indexOf('.') + 1)
 32           ) > -1">
 33         </video>
 34 
 35         <!-- 图片 -->
 36         <img
 37           class="el-upload-list__item-thumbnail"
 38           :src="file.url"
 39           alt=""
 40           @click="handlePreview(file, 'img')"
 41           v-if="
 42             ['jpg', 'png', 'gif', 'jpeg', 'bmp', 'tif', 'avif', 'pcx', 'tga'].indexOf(
 43               file.name.substring(file.name.indexOf('.') + 1)
 44             ) > -1"
 45         />
 46 
 47         <el-progress
 48           type="circle"
 49           v-if="showProgress && file.url == uploadUrl"
 50           :percentage="Number(file.percentage)"
 51           :width="64">
 52         </el-progress>
 54       </div>
 55     </el-upload>
 56 
 57     <!-- 图片预览 -->
 58     <van-image-preview
 59       v-model="isShowPreview"
 60       :images="previewImages"
 61       @change="onChange"
 62       :closeable="true"
 63       :startPosition="startPosition">
 64       <template v-slot:index>第{{ imgIndex + 1 }}页</template>
 65     </van-image-preview>
 66 
 67     <!-- 视频预览 -->
 68     <div class="video-box" v-if="isShowVideoPreview">
 69       <van-icon name="cross" class="close" @click="isShowVideoPreview = !isShowVideoPreview" />
 70       <video :src="videoUrl" controls alt=""></video>
 71     </div>
 72 
 73     <!-- 提示 -->
 74     <el-row class="limit-tip">
 75       <span>{{ tip }}</span>
 76     </el-row>
 77   </div>
 78 </template>
 79 
 80 <script>
 81 const uidGenerator = () => {
 82   return '-' + parseInt(Math.random() * 10000 + 1, 10)
 83 }
 84 
 85 const getFileName = path => {
 86   if (path.lastIndexOf('\\') >= 0) {
 87     const reg = new RegExp('\\\\', 'g')
 88     path = path.replace(reg, '/')
 89   }
 90   return path.substring(path.lastIndexOf('/') + 1)
 91 }
 92 
 93 export default {
 94   name: 'EUploadImgVideo',
 95   props: {
 96     // 是否支持批量上传
 97     multiple: {
 98       type: Boolean,
 99       default: () => false
100     },
101 
102     // 绑定的数据
103     value: {
104       type: [String, Array],
105       required: false
106     },
107 
108     // 禁用上传
109     disabled: {
110       type: Boolean,
111       default: false,
112       required: false
113     },
114 
115     // 文件类型
116     accept: {
117       type: String,
118       default: 'video/mp4,video/ogg,video/webm,image/png,image/jpg,image/gif,image/jpeg'
119     },
120 
121     // 上传时附带的额外参数
122     acceptData: {
123       type: Object,
124       default: () => {
125         return {
126           accept: '.mp4,.ogg,.webm,.png,.jpg,.gif,.jpeg', // 同时支持视频和图片时
127           maxsize: 1024 * 1024 * 500,
128           maxsize_text: '500M',
129           videoTypeList: ['video/mp4', 'video/ogg', 'video/webm'],
130           imageTypeList: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif']
131         }
132       }
133     },
134 
135     // 提示
136     tip: {
137       type: String,
138       default: '最多上传10个文件,仅支持 mp4, ogg, webm, png, jpg, gif, jpeg 格式'
139     },
140 
141     // 最大数量
142     limit: {
143       type: Number,
144       default: 10
145     },
146 
147     // 仅上传图片
148     onlyImage: {
149       type: Boolean,
150       default: () => false
151     },
152 
153     // 仅上传视频
154     onlyVideo: {
155       type: Boolean,
156       default: () => false
157     }
158   },
159 
160   watch: {
161     value: {
162       handler(val, oldValue) {
163         // 单选上传可以改变 fileList 的值,不用手动回显
164         // 批量上传时不能改变 fileList 的值,页面会报错,如果有新增页面和编辑页面,需要手动调用 $refs.xxx.initFileList() 来回显图片或视频
165         if (!this.multiple) {
166           this.initFileList(val)
167         }
168       },
169       // 立刻执行handler
170       immediate: true
171     }
172   },
173 
174   data() {
175     return {
176       fileList: [], // 上传的文件数据
177       isShowPreview: false, // 预览图片
178       isShowVideoPreview: false, // 预览视频
179       previewImages: [], // 预览的图片数据
180       previewVideo: [], // 预览的视频数据
181       imgIndex: 0, // 预览图片的索引
182       videoUrl: '', // 预览视频的url
183       startPosition: 0, // 预览图片的初始索引
184       showProgress: false,
185       uploadPercentage: 0
186     }
187   },
188 
189   methods: {
190     // 图片/视频回显
191     initFileList(array) {
192       if (!array || array.length === 0) {
193         this.fileList = []
194         return
195       }
196       const fileList = []
197       const arr = array || []
198       for (let a = 0; a < arr.length; a++) {
199         const url = this.getFileAccessHttpUrl(arr[a].url)
200         fileList.push({
201           name: getFileName(arr[a].name),
202           url: url,
203           uid: uidGenerator(),
204           type: arr[a].type,
205           response: {
206             status: 'history',
207             message: url
208           }
209         })
210       }
211       this.fileList = fileList
212     },
213 
214     // url加/dispatch前缀,用于预览使用
215     getFileAccessHttpUrl(url) {
216       if (url.indexOf('/dispatch') > -1) {
217         return url
218       } else {
219         return '/dispatch' + url
220       }
221     },
222 
223     // 上传文件地址
224     uploadApi() {
225       return '/dispatch/upload'
226     },
227 
228     // 其他附件上传大小限制处理
229     beforeUpload(file) {
230       const { videoTypeList, imageTypeList } = this.acceptData
231       let isVideo = true
232       if (videoTypeList) {
233         isVideo = videoTypeList.indexOf(file.type) !== -1
234       }
235 
236       let isImg = true
237       if (imageTypeList) {
238         isImg = imageTypeList.indexOf(file.type) !== -1
239       }
240 
241       // 只支持视频
242       if (this.onlyVideo) {
243         if (!isVideo) {
244           this.$toast(`只能上传${this.acceptData.accept}的文件!`)
245           return false
246         }
247       }
248 
249       // 只支持图片
250       if (this.onlyImage) {
251         if (!isImg) {
252           this.$toast(`只能上传${this.acceptData.accept}格式的文件!`)
253           return false
254         }
255       }
256 
257       // 默认:视频和图片都支持
258       if (!isVideo && !isImg) {
259         this.$toast(`只能上传${this.acceptData.accept}格式的文件!`)
260         return false
261       }
262 
263       // 文件大小限制
264       const isLessThanMaxSize = file.size < this.acceptData.maxsize
265       if (!isLessThanMaxSize) {
266         this.$toast(`上传文件大小不能超过${this.acceptData.maxsize_text}!`)
267         return false
268       } else {
269         this.$toast.loading({
270           message: '请您耐心等待,文件上传中...',
271           forbidClick: true,
272           duration: 0,
273           loadingType: 'spinner'
274         })
275       }
276       return true
277     },
278 
279     // 文件个数限制
280     handleExceed() {
281       this.$toast(`当前限制最多上传 ${this.limit} 个文件!`)
282     },
283 
284     // 预览图片
285     onChange(index) {
286       this.imgIndex = index
287     },
288 
289     handleChange(file, fileList, type) {
290       this.uploadPercentage = 0
291       if (file.status === 'ready') {
292         this.showProgress = true
293         this.uploadUrl = file.url
294         const interval = setInterval(() => {
295           if (this.uploadPercentage >= 95) {
296             clearInterval(interval)
297           }
298           this.uploadPercentage += 1
299         }, 20)
300       }
301       if (file.status === 'success') {
302         this.uploadUrl = file.url
303         this.uploadPercentage = 100
304         this.showProgress = false
305       }
306     },
307 
308     // 上传图片成功
309     handleSuccess(response, file, fileList) {
310       this.$toast.clear()
311       this.showProgress = false
312       if (response.code === 200) {
313         this.$toast('上传成功')
314         const attachment = fileList.map(item => {
315           if (item.response && item.response.data) {
316             item.url = '/dispatch' + item.response.data || ''
317           }
318           return {
319             name: item.name,
320             url: item.url,
321             uid: item.uid,
322             type: item.raw ? item.raw.type : item.type
323           }
324         })
325         this.fileList = attachment
326         // 回显给业务,不携带 /dispatch 前缀
327         this.$emit('change', this.fileList)
328       } else {
329         for (let i = 0; i < fileList.length; i++) {
330           if (i + 1 === fileList.length) {
331             fileList.splice(i, 1)
332           }
333         }
334         this.$toast(response.msg)
335       }
336     },
337 
338     // 图片/视频预览
339     handlePreview(file, type) {
340       this.previewImages = []
341       this.previewVideo = []
342       if (type === 'img') {
343         this.fileList.forEach((item, index) => {
344           if (item.url === file.url) {
345             this.startPosition = index
346           }
347         })
348       }
349 
350       this.fileList.map(item => {
351         // 预览图片的数据
352         const { imageTypeList, videoTypeList } = this.acceptData
353         let isImg = true
354         if (imageTypeList) {
355           isImg = imageTypeList.indexOf(item.type) !== -1
356         }
357         if (isImg) {
358           this.previewImages.push(item.url)
359         }
360 
361         // 预览视频的数据
362         let isVideo = true
363         if (videoTypeList) {
364           isVideo = videoTypeList.indexOf(item.type) !== -1
365         }
366         if (isVideo) {
367           this.previewVideo.push(item.url)
368         }
369       })
370 
371       if (type === 'img') {
372         this.isShowPreview = true
373       } else {
374         this.videoUrl = file.url
375         this.isShowVideoPreview = true
376       }
377     },
378 
379     // 删除
380     handleRemove(file) {
381       let index
382       this.fileList.find((item, idx) => {
383         if (item.uid === file.uid) {
384           index = idx
385           return
386         }
387       })
388       if (typeof index !== 'undefined') {
389         this.fileList.splice(index, 1)
390       }
391       // 回显给业务
392       this.$emit('change', this.fileList)
393     },
394 
395     // 长按保存图片
396     touchStart(url, file) {
397       clearTimeout(this.time)
398       this.time = setTimeout(() => {
399         // 这里就按照chrome等新版浏览器来处理
400         const link = document.createElement('a')
401         link.href = url
402         link.setAttribute('download', file.name)
403         link.click()
404       }, 1000)
405     },
406 
407     touchEnd() {
408       clearTimeout(this.time)
409     }
410   }
411 }
412 </script>
413 
414 <style lang="scss" scoped>
415 .custom-box-img {
416   position: fixed;
417   z-index: 1002;
418   left: 0;
419   right: 0;
420   top: 1rem;
421   display: flex;
422   bottom: 0;
423   background-color: #ffffff;
424   align-items: center;
425   .download-icon {
426     margin: 0 auto;
427     z-index: 1003;
428     color: #ffffff;
429     font-size: 18px;
430     margin-top: 10px;
431     ::v-deep .el-icon-download:before {
432       font-size: 20px;
433       color: #ffffff;
434       vertical-align: middle;
435     }
436     .el-button--primary.is-disabled {
437       background-color: #409eff;
438       border-color: #409eff;
439     }
440   }
441   .el-carousel__container {
442     height: calc(100vh - 26px);
443   }
444   .el-carousel__arrow {
445     background-color: rgba(31, 45, 61, 0.5);
446     line-height: 36px;
447   }
448   .el-carousel__indicators--outside {
449     bottom: 0;
450     line-height: 26px;
451   }
452 }
453 
454 /deep/ .el-upload-list--picture-card .el-upload-list__item-thumbnail {
455   width: 100%;
456   height: 100%;
457 }
458 .limit-tip {
459   color: #999999;
460   display: inline-block;
461   line-height: 20px;
462   margin-top: 5px;
463   font-size: 14px;
464 }
465 .video-box {
466   position: fixed;
467   top: 0;
468   left: 0;
469   width: 100%;
470   height: 100%;
471   background: #000;
472   z-index: 9999;
473   vertical-align: middle;
474   text-align: center;
475   video {
476     height: 60%;
477     margin-top: 20%;
478     width: 100%;
479   }
480   .close {
481     position: absolute;
482     top: 10px;
483     right: 10px;
484     color: #fff;
485     font-size: 28px;
486   }
487 }
488 
489 .custom-file-list {
490   position: relative;
491   height: 75px;
492   font-size: 16px;
493   .el-icon-close {
494     position: absolute;
495     right: 0;
496     top: 0;
497     z-index: 1000;
498     display: block !important;
499     font-size: 18px;
500     color: #ffffff;
501     background-color: #a9a6a6;
502     border-radius: 100%;
503   }
504 }
505 
506 ::v-deep .el-upload-list--picture-card .el-upload-list__item {
507   width: 75px;
508   height: 75px;
509 }
510 ::v-deep .el-upload--picture-card {
511   width: 75px;
512   height: 73px;
513   line-height: 78px;
514   background-color: #f9f9f9;
515 }
516 ::v-deep .el-upload-list--picture-card .el-progress {
517   width: 75px !important;
518 }
519 ::v-deep .el-progress-circle {
520   width: 75px !important;
521   height: 75px !important;
522 }
523 ::v-deep .van-image-preview {
524   top: 40px;
525   height: 95%;
526 }
527 </style>

 

posted @ 2022-10-08 10:38  我就尝一口  阅读(1199)  评论(0编辑  收藏  举报