二次封装ele-ui上传公共组件(支持多选,放大,删除)

在compnents新建一个uploader.vue文件
<template>

  <dir class="element-ui-uploader-extension">

    <div class="file-list row">

      <div class="file" v-for="obj in fileList" :key="obj.uid" :style="previewStyle">

        <img class="img-cover" v-if="obj.status === 'success' && type === 'image'" :src="obj.path">

        <video class="img-cover" v-else-if="obj.status === 'success' && type === 'video'" :src=" obj.path"></video>

        <!-- 默认图 -->

        <img class="img-cover" v-else-if="obj.status === 'uploading'" src="/img/image_default.png">

        <!-- 进度条 -->

        <el-progress v-if="obj.status === 'uploading'" class="file-progress" type="circle" :width="50" :stroke-width="2" :percentage="Number(obj.percentage.toFixed(2))"></el-progress>

        <div v-if="obj.status === 'success'" class="control-mask">

          <i class="el-icon-zoom-in" @click="view(obj)"></i>

          <i v-if="!disabled" class="el-icon-delete" @click="del(obj)"></i>

        </div>

      </div>

      <el-upload v-if="!disabled" ref="upload" v-show="fileList.length < max"  accept=".png, .jpg, .jpeg" :action="uploadUrl" :show-file-list="false" :limit="max" :multiple="multiple" :disabled="disabled" :headers="headers" :on-change="change" :on-exceed="exceed" :on-progress="progress" :before-upload="beforeUpload" :on-success="success" :on-error="error" :on-remove="remove" :before-remove="beforeRemove">

        <div class="placeholder" :style="previewStyle">

          <i class="el-icon-plus" title="上传"></i>

          <span class='placeholder-text'>{{ placeholderText }}</span>

        </div>

      </el-upload>

    </div>

    <el-dialog v-if="dialogVisible" width="800px" :visible.sync="dialogVisible" :close-on-click-modal="false" append-to-body :lock-scroll="false">

      <img style="width: 100%;height:100%;object-fit:cover" v-if="type === 'image'" class="img-cover" :src="dialogImageUrl" alt="">

      <video v-if="type === 'video'" class="img-cover" :src="dialogImageUrl" controls="controls"></video>

    </el-dialog>

  </dir>

</template>

<script>

import {getToken} from '@/util/auth'; // 获取上传token

export default {

  props: {

    type: {

      // 上传文件类型 [image, video, file]

      type: String,

      default: function () {

        return 'image'

      }

    },

    value: {

      // 表单值 用逗号分隔的资源相对地址

      // type: String,

      required: true

    },

    max: {

      // 上传文件数量

      type: Number,

      default: function () {

        return 1

      }

    },

    maxMB: {

      // 上传文件大小 单位 MB

      type: Number,

      default: 2

    },

    accept: {

      // 上传文件格式

      type: Array,

      default: function () {

        const accept = {

          //  image: [

          //  'jpg',

          //  'jpeg',

          //  'png',

          //  'gif',

          //  'bmp',

          //  'JPG',

          //  'JPEG',

          //  'PNG',

          //  'GIF',

          //  'BMP'

          // ],

          image: [

            'jpg',

            'jpeg',

            'png',

          ],

          // video : ['flv', 'mpg', 'mpeg', 'avi', 'wmv', 'mov', 'asf', 'rm', 'rmvb', 'mkv', 'm4v', 'mp4'],

          // 考虑视频播放兼容性

          video: ['mp4', 'mpg', 'mpeg'],

          file: ['ppt', 'pptx', 'doc', 'docx', 'xls', 'xlsx', 'pdf']

        }

        return accept[this.type]

      }

    },

    disabled: {

      // 禁用

      type: Boolean,

      default() {

        return false

      }

    },

    fileSavePath: String, // 要传给服务端的保存文件夹

    width: Number, // 图片宽度限制 需要配合高度使用,单一设置无效

    height: Number, // 图片高度限制 需要配合宽度使用,单一设置无效

    previewStyle: {

      // 展示样式

      type: Object,

      default() {

        return {

          width: '138px',

          height: '138px'

        }

      }

    },

      /* 提示文字内容 */

      placeholderText: {

          type: String,

          default() {

              return '大小 < 2M;长宽=3:2'

          }

      }

  },

  created() {

    this.getValue()

  },

  data() {

    return {

      dialogImageUrl: '',

      dialogVisible: false,

      fileList: [],

      uploadUrl: '/api/blade-resource/oss/endpoint/put-file', // 上传地址

    }

  },

  computed: {

    multiple() {

      return this.max > 1

    },

    headers() {

      return {

        'Blade-Auth': 'bearer '+getToken()

      }

    }

  },

  watch: {

    value() {

      this.getValue()

    }

  },

  methods: {

    getUUID() {

      var s = []

      var hexDigits = '0123456789abcdef'

      for (var i = 0; i < 36; i++) {

        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)

      }

      s[14] = '4'

      s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1)

      s[8] = s[13] = s[18] = s[23] = '-'

      return s.join('')

    },

    getValue() {

      this.fileList = []

      if (!this.value) {

          if (this.$refs.upload) {

              this.$refs.upload.clearFiles()

          }

          return

      }

      const arr = this.value.split(',')

      if (arr.length === 1) {

          this.fileList = [{ name: '图片-1', path: arr[0], status: 'success' }]

      } else if (arr.length > 1) {

          arr.forEach((el, i) => {

              const obj = this.fileList.find((item) => {

                  return item.path === el

              })

              if (!obj && el) {

                  this.fileList.push({

                      name: '图片-' + (i + 1),

                      path: el,

                      uid: this.getUUID(),

                      status: 'success'

                  })

              }

          })

      } else {

          return

      }

    },

    // 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。

    beforeUpload(file) {

      const maxMB = this.maxMB

      const accept = this.accept

      const format = file.type.split('/')[1]

      const isFormat = accept.indexOf(format) != -1

      const isSize = file.size < maxMB * 1024 * 1024

      // console.log(file, fileList)

      // console.log(format, isFormat, isSize)

      // 检验文件格式

      if (!isFormat) {

        this.$message.error(

          `文件格式错误,请上传${accept.join(',')}格式的文件`

        )

        return false

      }

      // 校验文件大小

      if (!isSize) {

        this.$message.error(`文件太大,请上传${maxMB}MB内的文件`)

        return false

      }

      // 校验图片尺寸

      return this.imgScale(file)

    },

    // 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。

    beforeRemove(file) {

      // console.log(file, fileList)

      if (file.status === 'success') {

        return this.$confirm(`确定移除 ${file.name}?`)

      }

    },

    // 文件超出个数限制时的钩子

    exceed(files, fileList) {

      console.log(files, fileList)

      this.$message.error(`超出数量限制,请选择${this.max}个文件`)

    },

    // 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用

    change(file, fileList) {

      console.log(file, fileList)

    },

    // 文件上传时的钩子

    progress(event, file) {

      // console.log(event, file, fileList, file.status)

      this.fileList = this.fileList.filter((el) => {

        return el.uid !== file.uid

      })

      this.fileList.push(file)

    },

    // 文件上传成功时的钩子

    success(response, file, fileList) {

      console.log(response, file, fileList, file.status)

      this.fileList.forEach((el) => {

        if (el.uid === file.uid) {

          el.path = file.response.data.link

        }

      })

      this.setValue()

    },

    // 文件上传失败时的钩子

    error(err, file, fileList) {

      console.log(err, file, fileList, file.status)

    },

    // 文件列表移除文件时的钩子

    remove(file, fileList) {

      console.log(file, fileList, file.status)

      this.fileList = this.fileList.filter((el) => {

        return el.uid !== file.uid

      })

    },

    // 校验图片尺寸

    imgScale(file) {

      // 没有限制宽高,直接上传

      const width = this.width

      const height = this.height

      if (!width || !height) return true

      return new Promise((resolve, reject) => {

        let reader = new FileReader()

        reader.file = file

        reader.readAsDataURL(file)

        reader.onload = (res) => {

          const result = res.target.result

          const img = new Image()

          img.onload = () => {

            if (

              img.height !== parseFloat(height) ||

              img.width !== parseFloat(width)

            ) {

              this.$message.error(

                `图片尺寸错误,请上传${this.width}px*${this.height}px的图片`

              )

              reject(false)

            } else {

              resolve(true)

            }

          }

          img.src = result

        }

      })

    },

    // 查看已上传图片

    view(file) {

      const obj = this.fileList.find((el) => {

        return el.uid === file.uid

      })

      this.dialogImageUrl =  obj.path

      this.dialogVisible = true

    },

    // 删除已上传图片

    del(file) {

      this.$confirm(`确定移除?`, { type: 'warning' })

        .then(() => {

          this.fileList = this.fileList.filter((el) => {

            return el.uid !== file.uid

          })

          this.setValue()

          this.$refs.upload.clearFiles()

          this.$message({

            type: 'success',

            message: '删除成功!'

          })

        })

        .catch(() => {

          this.$message({

            type: 'info',

            message: '已取消删除'

          })

        })

      // this.fileList = this.fileList.filter((el) => {

      //  return el.uid !== file.uid

      // })

      // this.setValue()

      // this.$refs.upload.clearFiles()

      // this.$message({

      //  type: 'success',

      //  message: '删除成功!'

      // })

    },

    setValue() {

      const value = this.fileList

        .map((el) => {

          return el.path

        })

        .join(',')

      this.$emit('input', value) // 传入父组件接受处理数据

      this.$emit('checkValidate') // 需要效验就触发对应多validateField(‘name’)

    }

  }

}

</script>

<style lang="scss" scoped>

.element-ui-uploader-extension {

  padding: 0;

  margin: 0px;

  .placeholder {

    border: 1px dashed #dddddd;

    border-radius: 5px;

    display: flex;

    flex-direction: column;

    align-items: center;

    justify-content: center;

    font-size: 20px;

    color: #aaaaaa;

    margin-right: 10px;

    &:hover {

      border: 1px dashed #409eff;

    }

    .placeholder-text{

      display: inline-block;

      width: 100%;

      font-size: 10px;

      transform: scale(0.8);

      line-height: 14px;

      color: #C0C4D6;

    }

  }

  .file-list {

    display: flex;

    flex-wrap: wrap;

    align-items: center;

  }

  .file {

    position: relative;

    margin-right: 10px;

    border: 1px dashed #dddddd;

    border-radius: 4px;

    overflow: hidden;

    .img-cover{

      width: 100%;

      height: 100%;

      object-fit: cover;

    }

    .file-progress {

      position: absolute;

      left: 0;

      right: 0;

      bottom: 0;

      top: 0;

      margin: auto;

      width: 50px;

      height: 50px;

      .el-progress-bar__outer,

      .el-progress-bar__inner {

        border-radius: 0;

      }

    }

    .control-mask {

      display:none;

      position: absolute;

      left: 0;

      right: 0;

      bottom: 0;

      top: 0;

      width: 100%;

      height: 100%;

      background: rgba(0,0,0, 0.5);

      align-items: center;

      justify-content: space-evenly;

      i {

        font-size: 20px;

        color: #fff;

        // background: rgba(255, 255, 255, 0.8);

        width: 30px;

        height: 30px;

        border-radius: 50%;

        display: flex;

        align-items: center;

        justify-content: center;

        cursor: pointer;

        &:hover {

          // background: rgba(255, 255, 255, 1);

          color: #fff;

        }

      }

    }

    &:hover {

      .control-mask {

        display: flex;

      }

    }

  }

}

</style>

父组件引用

import uploader from '@/components/form/uploader.vue'


好了这样一个公共组件就封装完了,工作中用起来就很方便,统一样式管理.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容