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(讲解有挂)在进入广西老友玩老是输怎...
法门辅助!福建13水插件(辅助... 法门辅助!福建13水插件(辅助挂)一贯是有辅助技巧(有挂技术)1、许多玩家不知道福建13水插件辅助怎...
办法辅助!潮友会app下载官方... 办法辅助!潮友会app下载官方辅助器(辅助挂)真是真的是有辅助app(有挂教程)该软件可以轻松地帮助...
妙招辅助!邯郸胡乐挂辅助(辅助... 妙招辅助!邯郸胡乐挂辅助(辅助挂)好像存在有辅助插件(有挂方略)1、上手简单,内置详细流程视频教学,...
教程书辅助!乐酷辅助(辅助挂)... 教程书辅助!乐酷辅助(辅助挂)其实存在有辅助脚本(有挂细节)乐酷辅助能透视中分为三种模型:乐酷辅助模...
学习辅助!决战卡五星辅助(辅助... 学习辅助!决战卡五星辅助(辅助挂)本来真的是有辅助软件(有人有挂)学习辅助!决战卡五星辅助(辅助挂)...
绝活辅助!边锋嘉兴麻将辅助器(... 绝活辅助!边锋嘉兴麻将辅助器(辅助挂)真是真的有辅助神器(新版有挂)1、边锋嘉兴麻将辅助器公共底牌简...
举措辅助!枫叶辅助器(辅助挂)... 举措辅助!枫叶辅助器(辅助挂)本来存在有辅助技巧(竟然有挂)1、下载好枫叶辅助器正确养号方法之后点击...
讲义辅助!点我达辅助(辅助挂)... 讲义辅助!点我达辅助(辅助挂)一直存在有辅助技巧(有人有挂)1、点我达辅助辅助器安装包、点我达辅助辅...
模块辅助!威信茶馆有挂的吗(辅... 模块辅助!威信茶馆有挂的吗(辅助挂)一直真的是有辅助脚本(揭秘有挂)1、玩家可以在威信茶馆有挂的吗线...