Vue3 + cropper 实现裁剪头像的功能(裁剪效果可实时预览、预览图可下载、预览图可上传到SpringBoot后端、附完整的示例代码和源代码)
创始人
2024-11-14 07:06:24
0

文章目录

  • 0. 前言
  • 1. 裁剪效果(可实时预览)
  • 2. 安装 cropper
  • 3. 引入 Vue Cropper
    • 3.1 局部引入(推荐使用)
    • 3.2 全局引入
  • 4. 在代码中使用
    • 4.1 template部分
    • 4.2 script部分
  • 5. 注意事项
  • 6. SpringBoot 后端接收图片
    • 6.1 UserController.java
    • 6.2 Result.java
  • 7. 完整的示例代码
    • 7.1 Homeview.vue
    • 7.2 request.js
    • 7.3 main.js
    • 7.4 vite.config.js
  • 8. 完整的源代码

0. 前言

裁剪头像的需求十分常见,主要目的是为了统一用户头像的尺寸,避免因为用户上传的图片尺寸大小不一致导致页面布局出现问题

高效实现需求的方法,就是避免重复造轮子,在这里推荐使用 cropper 实现头像裁剪功能 (原因是 cropper 功能强大、上手简单、文档详细)


cropper 的Gitee地址:vue-cropper

cropper Vue3在线示例:cropper Vue3在线示例

1. 裁剪效果(可实时预览)

在这里插入图片描述

2. 安装 cropper

# npm 安装 npm install vue-cropper@next 
# yarn 安装 yarn add vue-cropper@next 

3. 引入 Vue Cropper

3.1 局部引入(推荐使用)

哪个组件需要使用 Vue Cropper,就在哪个组件导入

import 'vue-cropper/dist/index.css' import { VueCropper }  from 'vue-cropper' 

3.2 全局引入

main.js 文件

import VueCropper from 'vue-cropper' import 'vue-cropper/dist/index.css'  const app = createApp(App) app.use(VueCropper) app.mount('#app') 

4. 在代码中使用

注意事项:

要为 组件设置宽和高,并用一个外层容器包裹 组件

4.1 template部分

 

4.2 script部分

const option = ref({   autoCrop: true, // 是否默认生成截图框   autoCropHeight: '240px', // 默认生成截图框宽度(默认值:容器的 80%, 可选值:0 ~ max), 真正裁剪出来的图片的宽度为 autoCropHeight * 1.25   autoCropWidth: '240px', // 默认生成截图框宽度(默认值:容器的 80%, 可选值:0 ~ max), 真正裁剪出来的图片的宽度为 autoWidth * 1.25   canMove: true, // 上传图片是否可以移动   canScale: true, // 图片是否允许滚轮缩放   centerBox: true, // 截图框是否被限制在图片里面   fixed: true, // 是否固定截图框的宽高比例   fixedBox: true, // 是否固定截图框大小   fixedNumber: [1, 1], // 截图框的宽高比例([ 宽度 , 高度 ])   img: 'https://img2.baidu.com/it/u=2339635883,2403687892&fm=253&fmt=auto&app=138&f=JPEG', // 裁剪图片的地址(可选值:url 地址, base64, blob)   infoTrue: true, // infoTrue为 true 时显示预览图片的宽高信息,infoTrue为 false 时表示显示裁剪框的宽高信息   mode: 'contain', // 截图框可拖动时的方向(可选值:contain , cover, 100px, 100% auto)   origin: false, // 上传的图片是否按照原始比例渲染   outputSize: 1, // 裁剪生成图片的质量(可选值:0.1 ~ 1)   outputType: 'png', // 裁剪生成图片的格式(可选值:png, jpeg, webp) })  // 实时预览 const realTime = (data) => {   // console.log('realTime data =', data)   previews.value = data } 

5. 注意事项

  1. cropper 对象的 getCropBlob 方法和 getCropData 方法都是异步方法
  2. 虽然 getCropBlob 获取的的 Blob 对象在控制台打印时只有 size 和 type 属性,但是仍然可以使用window.URL.createObjectURL(blob)来生成 url ,从 Java 的角度来说,相当于重写了 Blob 类的 toString 方法
  3. 前端用 formData 上传文件时, key 要与后端接口中 @RequestParam(“avatar”) 指定的参数名一致

在这里插入图片描述

6. SpringBoot 后端接收图片

后端环境:

  • JDK:17.0.7
  • SpringBoot:3.0.2

6.1 UserController.java

import cn.edu.scau.controller.vo.Result; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;  import java.io.File; import java.io.IOException; import java.util.Objects; import java.util.UUID;  @RestController @RequestMapping("/user") public class UserController {      @PostMapping("/updateAvatar")     public Result updateAvatar(@RequestParam("avatar") MultipartFile avatar) {         System.err.println("文件名:" + avatar.getOriginalFilename());         System.err.println("文件大小(KB):" + avatar.getSize() / 1024);          try {             // 拿到图片文件后,可以将图片上传到阿里云、腾讯云、minio等第三方存储服务,然后返回图片的访问地址             // 这里直接保存到本地              String fileName = UUID.randomUUID().toString();             String suffix = Objects.requireNonNull(avatar.getOriginalFilename()).substring(avatar.getOriginalFilename().lastIndexOf("."));             avatar.transferTo(new File("F:\\Blog\\crop-avatar\\" + fileName + suffix));         } catch (IOException ioException) {             throw new RuntimeException(ioException);         }          return Result.success();     }  }  

6.2 Result.java

import java.io.Serializable;  /**  * 后端统一返回结果  *  * @param   */ public class Result implements Serializable {      private Integer code;     private String message;     private T data;      public static  Result success() {         Result result = new Result<>();         result.code = 200;         result.message = "success";         return result;     }      public static  Result success(T object) {         Result result = new Result<>();         result.data = object;         result.code = 200;         result.message = "success";         return result;     }      public static  Result fail(String message) {         Result result = new Result<>();         result.message = message;         result.code = 500;         return result;     }      public Integer getCode() {         return code;     }      public void setCode(Integer code) {         this.code = code;     }      public String getMessage() {         return message;     }      public void setMessage(String message) {         this.message = message;     }      public T getData() {         return data;     }      public void setData(T data) {         this.data = data;     }      @Override     public String toString() {         return "Result{" +                 "code=" + code +                 ", message='" + message + '\'' +                 ", data=" + data +                 '}';     }  } 

7. 完整的示例代码

7.1 Homeview.vue

     

7.2 request.js

import axios from 'axios'  const request = axios.create({   baseURL: '/api',   timeout: 60000,   headers: {     'Content-Type': 'application/json;charset=UTF-8'   } })  request.interceptors.request.use(  )  request.interceptors.response.use(response => {   if (response.data) {     return response.data   }   return response }, (error) => {   return Promise.reject(error) })  export default request 

7.3 main.js

import '@/assets/main.css'  import {createApp} from 'vue' import {createPinia} from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import zhCn from 'element-plus/es/locale/lang/zh-cn' import * as ElementPlusIconsVue from '@element-plus/icons-vue'  import App from './App.vue' import router from './router' import 'default-passive-events'  const app = createApp(App)  app.use(createPinia()) app.use(ElementPlus, {locale: zhCn}) for (const [key, component] of Object.entries(ElementPlusIconsVue)) {   app.component(key, component) } app.use(router)  app.mount('#app') 

7.4 vite.config.js

import {fileURLToPath, URL} from 'node:url'  import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue'  // https://vitejs.dev/config/ export default defineConfig({   plugins: [     vue()   ],   resolve: {     alias: {       '@': fileURLToPath(new URL('./src', import.meta.url))     }   },   server: {     proxy: {       '/api': {         target: 'http://localhost:8001',         changeOrigin: true,         rewrite: (path) => {           return path.replace('/api', '')         }       }     }   } }) 

8. 完整的源代码

前端:cropper-avatar-frontend

后端:cropper-avatar-backend

相关内容

热门资讯

透视app!aapoker怎么... 透视app!aapoker怎么设置抽水(透视)可以开辅助器(一贯是真的有挂)一、aapoker怎么设...
透视教程!哈糖大菠萝软件下载,... 透视教程!哈糖大菠萝软件下载,wepoker脚本(透视)原先有挂(科技教程)透视教程!哈糖大菠萝软件...
透视总结(WPK)确实有挂(透... 透视总结(WPK)确实有挂(透视)wpk辅助软件(攻略方法);1、wpk辅助软件透视辅助简单,wpk...
透视挂!pokernow辅助工... 透视挂!pokernow辅助工具,哈糖大菠萝怎么挂,真是是真的有挂(攻略教程)1、任何哈糖大菠萝怎么...
透视辅助!aapoker辅助工... 透视辅助!aapoker辅助工具安全吗(透视)发牌逻辑(总是有挂)1、超多福利:超高返利,海量正版游...
透视私人局!hh poker插... 透视私人局!hh poker插件下载,约局吧德州真的有透视挂吗(透视)起初存在有挂(技巧教程)1、构...
透视透视(WPK)切实真的有挂... 透视透视(WPK)切实真的有挂(透视)wpk有作弊吗(攻略方法)1)wpk有作弊吗辅助挂:进一步探索...
透视攻略!epoker有透视吗... 透视攻略!epoker有透视吗,拱趴大菠萝机器人,其实有挂(新2025教程);1、下载好拱趴大菠萝机...
透视科技!aapoker公共底... 透视科技!aapoker公共底牌(透视)辅助器是真的(竟然是有挂)1、每一步都需要思考,不同水平的挑...
透视好友!wepoker有没有... 透视好友!wepoker有没有挂,hhpoker辅助软件(透视)起初是真的有挂(解密教程)1、hhp...