vue封装组件双向绑定v-model

本文展示了如何基于ElementUI组件封装一个高级的图片上传组件,该组件支持v-model双向绑定,包含图片预览、尺寸验证、大小限制等功能。在组件中,通过watch监听v-model值的变化,并在上传成功后通过$emit('input')更新父组件的值。此外,还详细介绍了组件的样式设计和交互处理,如上传进度显示、删除图片等操作。
摘要由CSDN通过智能技术生成

封装组件的v-model

再看组件的时候,不如elementui等等,我们时常看到它们的组件都是直接通过v-model双向绑定的,而不是通过我们常用的属性(prop)传递过去,子组件修改通过$emit,或者通过vuex等等来返回父组件,这样的方法也不是说不行,但是总感觉没有elementui那样的写法高级。所以我们也来简单的封装一个看起来高级一点点的组件。
简单的借用elementui的组件做一个二开

<template>
  <div class="image-video-upload">
    <div v-if="imageUrl" class="upload-success">
      <custom-image :src="imageUrl" class="result-image" :style="imageStyle" />
      <i v-if="!disabled" class="el-icon-circle-close" @click="handleRemove" />
    </div>
    <el-upload
      v-show="!imageUrl"
      ref="uploader"
      class="uploader"
      :action="action"
      :disabled="disabled"
      :headers="headers"
      :accept="accept"
      :show-file-list="false"
      :on-success="handlerSuccess"
      :on-error="handlerError"
      :before-upload="beforeUploadHandler"
      :style="imageStyle"
      :on-progress="handleProcess"
      :data="uploadData"
    >
      <el-progress v-if="uploading" type="circle" :percentage="percentage" :width="80" />
      <i v-else class="el-icon-plus" />
    </el-upload>
    <div class="el-upload__tip" v-html="tip" />
  </div>
</template>
export default {
  name: 'UploadImage',
  components: {
    CustomImage
  },
  props: {
  //v-model直接使用value
    value: {
      type: String,
      default: ''
    },
    bucket: {
      type: String,
      default: 'knight-dev'
    },
    tip: {
      type: String,
      default: ''
    },
    // 控制图片样式(长宽等)
    imageStyle: {
      type: String,
      default: 'width: 200px;height: 100px;'
    },
    accept: {
      type: String,
      default: '.jpg,.jpeg,.png,.gif,.bmp,.JPG,.JPEG,.PBG,.GIF,.BMP'
    },
    width: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    // 图片尺寸验证类型
    validType: {
      type: Number,
      default: 0 // 0:比列验证,1:实际大小验证
    },
    // 图片尺寸验证
    valid: {
      type: Number,
      default: 0 // 0: 不验证,不提示  1:验证提示,不停止上传 2: 验证提示,停止上传
    },
    // 图片大小验证,单位M
    imageSize: {
      type: Number,
      default: 3
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      uploading: false,
      percentage: 0,
      imageUrl: this.value ? downloadUrl + '/' + this.value : '',
      action: uploadUrl
    }
  },
  computed: {
    uploadData() {
      return { bucket: this.bucket }
    },
    headers() {
      const header = {}
      const { access_token,  login_name } = this.$store.getters.authInfo
      header['Authorization'] = 'Bearer ' + access_token
      
      return header
    }
  },
  watch: {
    value(value) {
      this.imageUrl = value ? downloadUrl + '/' + value : ''
    }
  },
  methods: {
    handleProcess(event, file, fileList) {
      this.percentage = +file.percentage.toFixed(0)
    },
    beforeUploadHandler(file) {
      this.uploading = true
      this.percentage = 0
      const fileSize = file.size / 1024 / 1024
      if (fileSize > this.imageSize) {
        this.uploading = false
        this.$message.error(`图片文件大小不能超过 ${this.imageSize}MB!`)
        return false
      } else if (this.valid == 0 || this.width == 0 || this.height == 0) {
        return true
      }

      return new Promise((resolve, reject) => {
        this.filetoDataURL(file, url => {
          this.dataURLtoImage(url, img => {
            const { width, height } = this

            let validTemp = true
            let str = ''
            if (this.validType == 0) {
              validTemp =
                  (width / height).toFixed(2) ==
                  (img.width / img.height).toFixed(2)
              str = '比例'
            } else {
              validTemp = width == img.width && height == img.height
              str = '宽高'
            }
            if (!validTemp) {
              this.$message.error(`图片${str}不符合建议要求!`)
              if (this.valid == 2) {
                this.uploading = false
                return reject()
              }
            }
            resolve()
          })
        })
      })
    },
    handlerSuccess(res) {
      if (res.code == 200) {
        const imageUrl = res.data.fileName[0]
        this.uploading = false
        this.setValue(imageUrl)
      }
    },
    handlerError() {
      this.uploading = false
    },
    setValue(imageUrl) {
      this.imageUrl = imageUrl
      //返回值直接使用$emit('input','value')即可
      this.$emit('input', imageUrl)
    },
    handleRemove() {
      this.setValue('')
    }
  }
}
.image-video-upload {
  display: flex;

  .uploader,
  .result-image {
    display: flex;
    font-size: 28px;
    color: #8c939d;
    text-align: center;
    background-color: #fbfdff;
    border: 1px dashed #c0ccda;
    border-radius: 6px;
    cursor: pointer;
  }

  .result-image {
    border: 1px solid #c0ccda;
  }

  .uploader{
    &:hover {
      border-color: #409eff;
      color: #409eff;
    }

    ::v-deep {
      .el-upload{
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;

        &:focus{
          border-color: #fbfdff;
          color: #8c939d;
        }

        &:hover{
          border-color: #409eff;
          color: #409eff;
        }
      }
    }
  }

  .el-upload__tip {
    flex: 1;
    line-height: 21px;
    display: flex;
    align-items: center;
    margin-left: 20px;
    margin-top: 0;
  }

  .upload-success {
    position: relative;
    border-radius: 6px;
    overflow: hidden;

    &:hover {
      .icon-success {
        display: none;
      }

      .remove-wrapper {
        display: flex;
      }
    }

    .el-icon-circle-close{
      position: absolute;
      top: 0;
      right: 0;
      font-size: 20px;
      color: #F56C6C;
    }
  }

  .remove-wrapper {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
    background-color: rgba(0, 0, 0, 0.5);
  }

  .icon-success {
    position: absolute;
    right: -15px;
    top: -6px;
    width: 40px;
    height: 24px;
    // line-height: 24px;
    text-align: center;
    background: #13ce66;
    -webkit-transform: rotate(45deg);
    transform: rotate(45deg);
    -webkit-box-shadow: 0 0 1pc 1px rgba(0, 0, 0, 0.2);
    box-shadow: 0 0 1pc 1px rgba(0, 0, 0, 0.2);

    .el-icon-check {
      color: #ffffff;
      -webkit-transform: rotate(-45deg);
      transform: rotate(-45deg);
    }
  }
  .el-icon-delete {
    font-size: 20px;
    color: #fff;
    cursor: pointer;
  }
}

这样使用的时候就可以直接引入组件使用了,绑定的值也是可以通过v-midel双向了
这就是最终的呈现效果啦!!!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值