使用vue-cropper封装一个上传头像的组件(附完整代码)

效果展示

在这里插入图片描述

思路

  1. 点击按钮打开dialog弹窗
  2. 选择图片文件,打开vue-cropper来调整、裁剪图片
  3. 点击确定开始裁剪图片,然后将图片发送到后端,后端上传到OSS对象存储
  4. 最后,后端返回裁剪好的图片在OSS的路径,显示出这个裁剪好的图片

完整代码

CutPortrait.vue
<template>
  <div>
    <el-button @click="isShowDialog = true">编辑头像</el-button>
    <el-dialog title="设置头像" :visible.sync="isShowDialog" width="1000px" @close="closeDialog">
      <input type="file" style="display:none" id="input-file" accept="image/png, image/jpeg, image/jpg, image/bmp" @change="uploadImg($event)" />
      <el-row>
        <el-col :span="16" style="height: 400px;">
          <div class="container-left">
            <div v-if="isShowFileButton" class="btn-box">
              <el-popover width="200" trigger="hover" :content="'图片不可大于 ' + imageMaxSize + ' MB'">
                <div class="choose-file-btn" @click="changeFile" slot="reference">选 择 图 片</div>
              </el-popover>
            </div>
            <div v-else class="cropper-box">
              <vueCropper
                style="width:550px;height:400px;margin-left: 40px"
                ref="cropper"
                :img="options.img"
                :auto-crop="options.autoCrop"
                :fixed-box="options.fixedBox"
                :can-move-box="options.canMoveBox"
                :auto-crop-width="options.autoCropWidth"
                :auto-crop-height="options.autoCropHeight"
                :center-box="options.centerBox"
                @realTime="realTime"
              >
              </vueCropper>
            </div>
          </div>
        </el-col>
        <el-col :span="8" style="height: 400px;">
          <h2 style="text-align: center; line-height: 50px">头像预览</h2>
          <div class="show-preview">
            <div style="height: 200px; width: 200px" class="preview">
              <img :src="previews.url" :style="previews.img" alt="" />
            </div>
          </div>
          <div v-show="!isShowFileButton" class="preview-footer">
            <el-button @click="changeFile">更 换 图 片</el-button>
          </div>
        </el-col>
      </el-row>
      <span slot="footer" class="dialog-footer">
        <el-button @click="isShowDialog = false">取 消</el-button>
        <el-button type="primary" @click="commitCut" v-loading.fullscreen.lock="fullscreenLoading" :disabled="Object.keys(previews).length === 0">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: 'CutPortrait',
  props: {
    // 上传地址
    UploadUrl: {
      type: String,
      require: true
    },
    // 上传文件的附带参数数据
    UploadData: {
      type: Object,
      require: true
    },
    // 图片的大小限制,默认 10 MB
    imageMaxSize: {
      type: Number,
      default: 10
    }
  },
  data() {
    return {
      isShowDialog: false, // 弹窗状态
      isShowFileButton: true, // 选择文件的按钮状态
      options: { // vue-cropper的参数
        img: '', //裁剪的图片blob地址
        autoCrop: true, //默认生成截图框
        fixedBox: true, //固定截图框大小
        canMoveBox: false, //截图框不能拖动
        autoCropWidth: 200, //截图框宽度
        autoCropHeight: 200, //截图框高度
        centerBox: true //截图框被限制在图片里面
      },
      previews: {}, // 预览图对象
      fullscreenLoading: false, // 全屏加载遮罩状态
      imageUrl: '' // 上传后的图片地址
    }
  },
  methods: {
    // 关闭截图对话框弹窗
    closeDialog() {
      this.isShowDialog = false
      this.previews = {}
      this.isShowFileButton = true
    },
    // 点击选择图片或更换图片按钮,变更裁剪图片
    changeFile() {
      document.getElementById('input-file').click()
    },
    // 变更裁剪图片
    uploadImg(e) {
      let file = e.target.files[0]
      // 判断文件格式是否符合要求规范
      if (!/\.(jpg|jpeg|png|bmp|JPG|PNG)$/.test(e.target.value)) {
        this.$message.error('图片类型必须是,jpeg,jpg,png,bmp中的一种')
        return false
      }
      // 判断文件是否超出大小限制
      if (file.size / 1024 / 1024 > this.imageMaxSize) {
        this.$message.error('图片不可大于 ' + this.imageMaxSize + ' MB')
        return false
      }
      //fileReader 接口,用于异步读取文件数据
      let reader = new FileReader()
      reader.readAsDataURL(file) //重要 以dataURL形式读取文件
      reader.onload = e => {
        // this.options.img = window.URL.createObjectURL(new Blob([e.target.result])) 转化为blob格式
        this.options.img = e.target.result
        // 转化为base64
        // reader.readAsDataURL(file)
        // 转化为blob
      }
      this.isShowFileButton = false
    },
    // 同步实时预览的数据
    realTime(data) {
      this.previews = data
    },
    //确认截图,上传到后台
    commitCut() {
      this.fullscreenLoading = true
      let formData = new FormData()
      this.$refs.cropper.getCropBlob(res => {
        //res是裁剪后图片的blob对象,遍历组件上传数据的对象 => 添加到formData对象中
        formData.append('file', res)
        for (let key in this.UploadData) {
          formData.append(key, this.UploadData[key])
        }
        this.$http.post(this.UploadUrl, formData).then(res => {
          res = res.data
          if (res.code === '000000') {
            console.log(res.msg)
            this.imageUrl = res.data.path
            this.closeDialog()
            this.fullscreenLoading = false
            this.SetUrl()
          } else {
            console.log('%c上传失败', 'color:#a00')
            this.fullscreenLoading = false
          }
        })
      })
    },
    // 将裁剪后的图片传出的父组件
    SetUrl() {
      this.$emit('getUrl', this.imageUrl)
    }
  }
}
</script>

<style lang="scss" scoped>
.container-left {
  width: 100%;
  height: 100%;
  .btn-box {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .cropper-box {
    width: 100%;
    height: 100%;
  }
}

.choose-file-btn {
  width: 160px;
  height: 80px;
  font-size: 22px;
  font-weight: 600;
  line-height: 80px;
  text-align: center;
  color: #333;
  border: #aaa 1px solid;
  cursor: pointer;
  transition: 0.25s;
  box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3);
}
.choose-file-btn:hover {
  color: #fff;
  background: #00d7c6;
  border: #00d7c6 1px solid;
  transform: scale(1.15);
}

.show-preview {
  display: flex;
  justify-content: center;
}

.preview {
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid #ccc;
  background: #ccc;
}

.preview-footer {
  width: 100%;
  display: flex;
  justify-content: center;
  padding-top: 15px;
}
</style>

调用组件的 Test.vue
<template>
  <div style="height: 100%; width: 100%; padding-top: 50px">
    <div style="height: 300px; width: 300px; margin: auto">
      <img v-if="imgPath === ''" src="../static/image/person-default.png" />
      <img v-else :src="imgPath" />
      <CutPortrait :upload-data="UploadData" :upload-url="UploadUrl" @getUrl="getUrl"></CutPortrait>
    </div>
  </div>
</template>

<script>
import CutPortrait from './CutPortrait'
export default {
  name: 'Test',
  components: {
    CutPortrait
  },
  data() {
    return {
      // 上传地址
      UploadUrl: 'http://localhost:8080/test/upload',
      // 上传时,附带的参数
      UploadData: { 
        userId: '1234567890000'
      },
      imgPath: '' // 裁剪好的头像
    }
  },
  methods: {
    // 获取头像裁剪组件返回的路径
    getUrl(path) {
      this.imgPath = path
    }
  }
}
</script>

<style scoped></style>

Java后端代码
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.annotations.ResultMap;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

@CrossOrigin
@Controller
@RequestMapping("/test")
public class TestUpload {

    @RequestMapping("/upload")
    @ResponseBody
    public String upload(@RequestParam String userId,
                         @RequestParam("file") MultipartFile file) throws IOException {
        String fileName = userId + ".jpg"; // 头像文件名为 用户id.jpg
        System.out.println("userId:" + userId);
        System.out.println("文件名:" + fileName);
        
        /* 上传到OSS,并返回文件地址 */
        String imgURL = OSSTool.uploadFile(fileName, file);
        
        Map data = new HashMap();
        Map map = new HashMap();
        data.put("path", imgURL);
        if (imgURL != null) {
            map.put("code", "000000");
            map.put("msg", "上传成功");
            map.put("data", data);
        } else {
            map.put("code", "23333");
            map.put("msg", "上传失败");
        }
        return JSON.toJSONString(map);
    }

}

参考

https://blog.csdn.net/weixin_39327044/article/details/89765109

Vant 4 是基于 Vue 的一套移动端轻量级 UI 组件库,而 vue-cropper 是一个用于裁剪图片的组件。如果你想在 Vant 4 中实现移动端头像上传使用 vue-cropper 进行裁剪,首先需要安装这两个依赖: ```bash npm install vant vant-image-crop @vue/cli-plugin-vuex ``` 然后在项目里引入组件,并在适当的地方使用它: ```html <template> <van-cell v-if="cropping" title="裁剪"> <van-image-crop :src="currentImage" :preview-src="currentImage" :output-width="100" :output-height="100"></van-image-crop> <van-button slot="actions" @click="finishCrop">确定</van-button> </van-cell> </template> <script> import { VanCell, VanImageCrop } from 'vant'; export default { components: { VanCell, VanImageCrop }, data() { return { cropping: false, currentImage: null // 用户选中的原始图片 URL 或者文件路径 }; }, methods: { pickAvatar() { this.$van.camera.open().then((res) => { if (res.type === 'image') { this.currentImage = res.path; this.cropping = true; // 开始裁剪 } }); }, finishCrop() { // 当用户点击确定时,处理裁剪后的图片 // 这里可以将裁剪结果上传至服务器或者保存到本地 console.log('Cropped image:', this.currentImage); this.cropping = false; } } }; </script> ``` 在这个例子中,`pickAvatar` 方法打开相机选择照片,`finishCrop` 方法则是在用户完成裁剪后触发,处理裁剪后的图片。记得根据实际需求替换 `console.log` 和后续的上传逻辑。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值