Vue图片裁剪功能实现代码

目录

一、效果展示:

1、表单的图片上传项:

2、裁剪框页面

二、代码部分

1、首先安装Vue-Cropper,基于此组件的基础上开发的裁剪页面

2、裁剪弹窗的组件编写:

3、【图片上传表单项】组件编写

一、效果展示: 1、表单的图片上传项:

- 新增时默认一个空白Input框

- 更新时展示以往上传存放的图片,

  - 点击【查看】浏览完整大小

  - 点击【删除】清空src地址,重新上传新照片

2、裁剪框页面

- 先选择裁剪的图片

- 右侧展示裁剪区域

- 支持放大缩小,图片旋转

- 点击【上传图片】调用后台上传接口进行上传

二、代码部分 1、首先安装Vue-Cropper,基于此组件的基础上开发的裁剪页面 npm install vue-cropper "vue-cropper": "^0.5.8" 2、裁剪弹窗的组件编写: <template> <div v-loading="loading" class="cropper-content" > <div class="cropper-box"> <div class="cropper"> <vue-cropper ref="cropper" :img="option.img" :output-size="option.outputSize" :output-type="option.outputType" :info="option.info" :can-scale="option.canScale" :auto-crop="option.autoCrop" :auto-crop-width="autoCropWidth" :auto-crop-height="autoCropHeight" :fixed="option.fixed" :fixed-number="option.fixedNumber" :full="option.full" :fixed-box="option.fixedBox" :can-move="option.canMove" :can-move-box="option.canMoveBox" :original="option.original" :center-box="option.centerBox" :height="option.height" :info-true="option.infoTrue" :max-img-size="option.maxImgSize" :enlarge="option.enlarge" :mode="option.mode" @realTime="realTime" @imgLoad="imgLoad" /> </div> <!--底部操作工具按钮--> <div class="footer-btn"> <div class="scope-btn"> <label class="btn" for="uploads" >选择图片</label> <input id="uploads" type="file" style="position:absolute; clip:rect(0 0 0 0);" accept="image/png, image/jpeg, image/gif, image/jpg" @change="selectImg($event)" > <el-button size="mini" type="danger" plain icon="el-icon-zoom-in" @click="changeScale(1)" >放大</el-button> <el-button size="mini" type="danger" plain icon="el-icon-zoom-out" @click="changeScale(-1)" >缩小</el-button> <el-button size="mini" type="danger" plain @click="rotateLeft" >↺ 左旋转</el-button> <el-button size="mini" type="danger" plain @click="rotateRight" >↻ 右旋转</el-button> </div> <div class="upload-btn"> <el-button size="mini" type="success" @click="uploadImg('blob')" >上传图片<i class="el-icon-upload" /></el-button> </div> </div> </div> <!--预览效果图--> <div class="show-preview"> <div :style="previews.div" class="preview" > <img :src="previews.url" :style="previews.img" > </div> </div> </div> </template> <script> import { VueCropper } from 'vue-cropper' import { uploadFile } from '@/api/smrz/setting' import { regularFileName } from '@/utils' export default { name: 'CropperImage', components: { VueCropper }, /* props: ['name2'],*/ props: { autoCropWidth: { // 默认生成截图框宽度 type: Number, default: 410 }, autoCropHeight: { // 默认生成截图框高度 type: Number, default: 150 }, busType: { type: String, default: 'advertPic' } }, data() { return { loading: false, name: this.Name, previews: {}, option: { img: '', // 裁剪图片的地址 outputSize: 1, // 裁剪生成图片的质量(可选0.1 - 1) outputType: 'jpeg', // 裁剪生成图片的格式(jpeg || png || webp) info: true, // 图片大小信息 canScale: true, // 图片是否允许滚轮缩放 autoCrop: true, // 是否默认生成截图框 // autoCropWidth: 410, 默认生成截图框宽度 // autoCropHeight: 150, 默认生成截图框高度 fixed: false, // 是否开启截图框宽高固定比例 fixedNumber: [1.53, 1], // 截图框的宽高比例 full: true, // false按原比例裁切图片,不失真 fixedBox: true, // 固定截图框大小,不允许改变 canMove: true, // 上传图片是否可以移动 canMoveBox: true, // 截图框能否拖动 original: true, // 上传图片按照原始比例渲染 centerBox: false, // 截图框是否被限制在图片里面 height: true, // 是否按照设备的dpr 输出等比例图片 infoTrue: false, // true为展示真实输出图片宽高,false展示看到的截图框宽高 maxImgSize: 3000, // 限制图片最大宽度和高度 enlarge: 1, // 图片根据截图框输出比例倍数 mode: '230px 150px' // 图片默认渲染方式 }, randomFileName: '' } }, methods: { // 初始化函数 imgLoad(msg) { console.log('工具初始化函数=====' + msg) }, // 图片缩放 changeScale(num) { num = num || 1 this.$refs.cropper.changeScale(num) }, // 向左旋转 rotateLeft() { this.$refs.cropper.rotateLeft() }, // 向右旋转 rotateRight() { this.$refs.cropper.rotateRight() }, // 实时预览函数 realTime(data) { this.previews = data }, // 选择图片 selectImg(e) { const file = e.target.files[0] if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) { this.$message({ message: '图片类型要求:jpeg、jpg、png', type: 'error' }) return false } // 转化为blob const reader = new FileReader() reader.onload = (e) => { let data if (typeof e.target.result === 'object') { data = window.URL.createObjectURL(new Blob([e.target.result])) } else { data = e.target.result } this.option.img = data } console.log(`file.name => ${file.name}`) // 转化为base64 reader.readAsDataURL(file) }, // 上传图片 uploadImg(type) { const _this = this if (type === 'blob') { // 获取截图的blob数据 this.$refs.cropper.getCropBlob(async(data) => { _this.loading = true const formData = new FormData() // formData.append('file', data, this.createNewFileName()) // if (this.autoCropWidth === 100) { // formData.append('subDir', 'exchange') // } else if (this.autoCropHeight === 80) { // formData.append('subDir', 'task') // } else { // formData.append('subDir', 'rotate') // } _this.randomFileName = this.createNewFileName() // 给blob对象的filename属性赋值文件名 formData.append('rpc', data, _this.randomFileName) // 给参数赋值文件名 formData.append('fileName', _this.randomFileName) formData.append('busType', _this.busType) /* this.fileName = data.file.name formData.append('fileName', this.fileName)*/ // 调用axios上传 /* const { data: res } = await _this.$http.post('/api/file/imgUpload', formData)*/ uploadFile(formData).then(res => { /* this.handleSuccess(res)*/ if (res.code === 200) { _this.$message({ message: '图片上传成功', type: 'success' }) // const data = res.data.replace('[', '').replace(']', '').split(',') // const imgInfo = { // name: 'DX.webp', // url: res.data.agentUrl, // storeUrl: res.data.storeUrl, // uploadResult: res.data.uploadResult // } // _this.$emit('uploadImgSuccess', imgInfo) // 添加随机生成的文件名 res.fileName = _this.randomFileName _this.$emit('uploadImgSuccess', res) } else { _this.$message({ message: '文件服务异常,请联系管理员!', type: 'error' }) } }).finally(() => { _this.loading = false }) }) /* if (flag) { this.$message.warning('请选择图片') }*/ } }, createNewFileName() { // const now = Date.now() // const fileName = now + '-' + Math.ceil(Math.random() * 100) // return fileName + '.webp' const fileName = regularFileName() return fileName + '.webp' } } } </script> <style scoped lang="scss"> .cropper-content { display: flex; display: -webkit-flex; justify-content: flex-end; .cropper-box { flex: 1; width: 100%; .cropper { width: auto; height: 300px; } } .show-preview { flex: 1; -webkit-flex: 1; display: flex; display: -webkit-flex; justify-content: center; .preview { overflow: hidden; border: 1px solid #67c23a; background: #cccccc; } } } .footer-btn { margin-top: 30px; display: flex; display: -webkit-flex; justify-content: flex-end; .scope-btn { display: flex; display: -webkit-flex; justify-content: space-between; padding-right: 10px; } .upload-btn { flex: 1; -webkit-flex: 1; display: flex; display: -webkit-flex; justify-content: center; } .btn { outline: none; display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; -webkit-appearance: none; text-align: center; -webkit-box-sizing: border-box; box-sizing: border-box; outline: 0; -webkit-transition: 0.1s; transition: 0.1s; font-weight: 500; padding: 8px 15px; font-size: 12px; border-radius: 3px; color: #fff; background-color: #409eff; border-color: #409eff; margin-right: 10px; } } </style>

需要更改成自己的上传接口:

import { uploadFile } from '@/api/smrz/setting'

后台接口参数如下,要求表单方式上传

/** * 上传附件 * * @param file 文件流(注意带文件后缀,统一使用.webp结尾) * @param fileName 文件名称(唯一性) * @param busType 业务类型(具体值参考ApiConstants类中FILE_开头常量说明) * @author wangkun * @createTime 2022/7/19 17:18 */ @PostMapping(value = "/file/upload", consumes = "multipart/form-data") public RpcResult uploadFile(@RequestParam(value = "rpc") MultipartFile file, @RequestParam(value = "fileName") String fileName, @RequestParam(value = "busType") String busType) {

在uploadImg函数这里,使用FormData对象包装请求参数

注意append方法,要给文件对象指定文件名,必须要入参第三个参数

否则默认名称blob

按实际接口对应调整参数即可

const formData = new FormData() _this.randomFileName = this.createNewFileName() // 给blob对象的filename属性赋值文件名 formData.append('rpc', data, _this.randomFileName) // 给参数赋值文件名 formData.append('fileName', _this.randomFileName) formData.append('busType', _this.busType) uploadFile(formData)

其它自定义参数,通过Props属性传入此组件

props: { autoCropWidth: { // 默认生成截图框宽度 type: Number, default: 410 }, autoCropHeight: { // 默认生成截图框高度 type: Number, default: 150 }, busType: { type: String, default: 'advertPic' } },

文件名的生成方法,就是当前时间按单位数值排序

实际使用根据业务实际情况改写

export function regularFileName() { const now = new Date() const year = now.getFullYear() const month = digitFix(now.getMonth() + 1) const dayOfMonth = digitFix(now.getDate()) const hour = digitFix(now.getHours()) const minute = digitFix(now.getMinutes()) const second = digitFix(now.getSeconds()) const millSecond = now.getMilliseconds() return `${year}${month}${dayOfMonth}${hour}${minute}${second}${millSecond}` }const fileName = `${regularFileName()} 3、【图片上传表单项】组件编写 <template> <div class="cropper-app"> <el-form ref="ruleForm" :model="formValidate" :rules="ruleValidate" label-width="110px" class="demo-ruleForm" > <el-form-item :label="label" prop="mainImage" > <div class="list-img-box"> <div v-if="formValidate.mainImage !== ''" class="img_div" style="height: 100px;" > <img :src="formValidate.mainImage" alt="图片找不到" > <a href="#" rel="external nofollow" > <div class="mask"> <h3 style=""> <i class="el-icon-zoom-in" @click="clickImg('zoom-in')" /> <i class="el-icon-delete" @click="clickImg('delete')" /> </h3> </div> </a> </div> <div v-else class="upload-btn" style="height: 100px;width: 200px" @click="uploadPicture('flagImg')" > <i class="el-icon-plus" style="font-size: 30px;" /> <!--<span>封面设置</span>--> </div> </div> <input v-model="formValidate.mainImage" type="hidden" placeholder="请添加封面" > </el-form-item> </el-form> <!-- 剪裁组件弹窗 --> <el-dialog v-if="cropperModel" title="图片剪切" :visible.sync="cropperModel" width="1020px" center append-to-body > <cropper-image v-if="cropperModel" ref="child" :auto-crop-width="autoCropWidth" :auto-crop-height="autoCropHeight" :bus-type="busType" @uploadImgSuccess="handleUploadSuccess" /> </el-dialog> <!--查看大封面--> <el-dialog title="" :visible.sync="imgVisible" center append-to-body > <img v-if="imgVisible" :src="imgUrl" style="width: 100%" alt="查看" > </el-dialog> </div> </template> <script> import CropperImage from '@/components/CropperImage' import { commonsDownloadAPI } from '@/api/smrz/setting' export default { name: 'Tailoring', components: { CropperImage }, props: { label: { type: String, default: '上传图片' }, url: { type: String }, autoCropWidth: { // 默认生成截图框宽度 type: Number, default: 410 }, autoCropHeight: { // 默认生成截图框高度 type: Number, default: 150 }, isSignFlag: { type: Boolean, default: false }, busType: { type: String, default: 'busType' } }, data() { var imageUrl2 = (rule, value, callback) => { if (!this.isSignFlag) { return callback() } if (!value) { return callback(new Error('请输上传图片')) } return callback() } return { formValidate: { mainImage: '' }, ruleValidate: { mainImage: [ /* { required: true, message: '请上传图片', trigger: 'blur' }*/ { required: true, validator: imageUrl2, trigger: 'blur' } ] }, // 裁切图片参数 cropperModel: false, cropperName: '', imgUrl: '', imgVisible: false, dialogImageUrl: '', dialogVisible: false } }, created() { this.formValidate.mainImage = this.url this.imgUrl = this.url }, methods: { validateForm() { this.$refs['ruleForm'].validate((valid) => { this.$emit('validVal', valid) }) }, // 封面设置 uploadPicture(name) { this.cropperName = name this.cropperModel = true }, // 图片上传成功后 async handleUploadSuccess(data) { // this.formValidate.mainImage = data.url // 图片回显 const { data: res2, code } = await commonsDownloadAPI({ fileName: data.fileName, busType: 'advertPic' }) const imgBase64 = code !== 200 ? '-1' : `data:image/jpeg;base64,${res2.data}` this.formValidate.mainImage = imgBase64 /* switch (data.name) { case 'flagImg': this.formValidate.mainImage = data.url console.log('最终输出' + data.name) console.log('最终输出2' + this.formValidate) break }*/ this.cropperModel = false this.$emit('uploadSuccess', data) }, clickImg(val) { if (val === 'delete') { this.formValidate.mainImage = '' this.$emit('deleteImage') } else if (val === 'zoom-in') { // this.imgUrl = this.formValidate.mainImage this.imgVisible = true } } } } </script> <style scoped> .upload-list-cover { position: absolute; top: 0; bottom: 0; left: 0; right: 0; display: flex; flex-wrap: wrap; justify-content: space-between; padding: 0 40px; align-items: center; background: rgba(0, 0, 0, 0.6); opacity: 0; transition: opacity 1s; } .cover_icon { font-size: 30px; } .upload-btn { display: -webkit-box; display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; border: 1px solid #cccccc; border-radius: 5px; overflow: hidden; box-shadow: 0 0 1px #cccccc; } .upload-btn:hover { border: 1px solid #69b7ed; } .upload-btn i { margin: 5px; } .img_div img { width: 200px !important; height: 100px !important; /* margin: 20px 400px 0 400px; position: relative; width: 531px; height: 354px;*/ } .mask { position: absolute; top: 0; left: 0; width: 200px; height: 100px; background: rgba(101, 101, 101, 0.6); color: #ffffff; opacity: 0; } .mask h3 { text-align: center; line-height: 60px; } .img_div a:hover .mask { opacity: 0.8; } </style>

表单项组件需要引入

1、裁剪组件

2、图片下载接口

import CropperImage from '@/components/CropperImage' import { commonsDownloadAPI } from '@/api/smrz/setting'

3、表单项设置了自定义校验

var imageUrl2 = (rule, value, callback) => { if (!this.isSignFlag) { return callback() } if (!value) { return callback(new Error('请输上传图片')) } return callback() }

就是检查src有没有地址或者base64资源,校验触发的效果:

4、图片上传后的回调处理:

上传成功后,回到表单页需要立即回显之前上传的图片

所以需要调用图片下载接口来获取刚刚上传的资源,

在这个回调方法中实现,因为下载接口提供的资源不是图片地址,而是返回Base64编码

这里我写的是base64编码资源的回显处理

实际使用根据业务实际情况改写

// 图片上传成功后 async handleUploadSuccess(data) { // this.formValidate.mainImage = data.url // 图片回显 const { data: res2, code } = await commonsDownloadAPI({ fileName: data.fileName, busType: 'advertPic' }) const imgBase64 = code !== 200 ? '-1' : `data:image/jpeg;base64,${res2.data}` this.formValidate.mainImage = imgBase64 /* switch (data.name) { case 'flagImg': this.formValidate.mainImage = data.url console.log('最终输出' + data.name) console.log('最终输出2' + this.formValidate) break }*/ this.cropperModel = false this.$emit('uploadSuccess', data) },

4、业务功能引用

引入表单项

import Tailoring from '@/components/Tailoring'

声明组件,并注入参数

<div class="ant-upload-preview"> <tailoring v-if="true" ref="child" label="广告图片" :is-sign-flag="true" :url="url" :bus-type="businessType" :auto-crop-height="80" :auto-crop-width="410" @uploadSuccess="uploadSuccess" @validVal="validVal" /> </div>

- url是一开始加载组件需要回显的图片资源地址  

- isSignFlag变量用来辅助自定义校验的,为false时直接放行校验,所以默认写死true

- bus-type是自定义的业务参数

- auto-crop的宽高用来配置裁剪的宽高,预览大小和裁剪大小合并使用这两个参数

上传成功的回调,uploadSuccess,可以在组件自定义需要的参数

这里是以图片名称作为记录主键,所以要传入这个文件名

实际使用根据业务实际情况改写

async uploadSuccess(res) { console.log(`上传结果 res -> ${JSON.stringify(res)}`) const fileName = res.fileName this.newId = fileName.substring(0, fileName.lastIndexOf('.')) },

校验值,应该是返回校验后的src值,但我这里没用上,所以不执行任何逻辑

validVal(val) {},

要触发【裁剪表单项】校验,使用

this.$refs.child.validateForm()

到此这篇关于Vue图片裁剪功能支持的文章就介绍到这了,更多相关vue图片裁剪内容请搜索易知道(ezd.cc)以前的文章或继续浏览下面的相关文章希望大家以后多多支持易知道(ezd.cc)!

推荐阅读

    vue项目一些常见问题

    vue项目一些常见问题,组件,样式,**样式污染问题**同样的样式不需要在每个组件都复制组件内单独的样式加外层class包裹。加scope。否则只是

    01-Vue项目实战-网易云音乐-准备工作

    01-Vue项目实战-网易云音乐-准备工作,网易,项目,前言在接下来的一段时间,我会仿照网易云音乐,利用Vue开发一个移动端的网易云音乐项目。在做

    01- 第一天 spring boot2.3.1 +vue3.0 后台管理系统的研发

    01- 第一天 spring boot2.3.1 +vue3.0 后台管理系统的研发,自己的,后台,后台框架一直想开发一套完全属于自己的后台,但是18年的时候,曾经答

    Vue项目中 App.vue文件

    Vue项目中 App.vue文件,文件,内容, 在App.vue文件中,定义了一个id为app的div,在这个div板块中放置Helloworld组件,文件内容如下图所示:在

    1-Vue构造函数的生成

    1-Vue构造函数的生成,函数,属性,版本:@2.6.10环境:web ;思维图:www.processon.com/view/link/5…我们使用的Vue是一个经过层层加强的构造函数

    vue的跨域是什么意思

    vue的跨域是什么意思,跨域,浏览器,代理,请求,服务器,同源策略,在vue中,跨域是指浏览器不能执行其他网站的脚本;它是浏览器同源策略造成的,是浏览器

    Vue中如何实现表单验证

    Vue中如何实现表单验证,验证,表单验证,表单,用户名,元素,指令,随着web应用的不断发展,表单验证逐渐成为web开发过程中不可或缺的一部分。在Vue中

    用vue框架有什么好处

    用vue框架有什么好处,组件,项目,数据,优化,操作,框架,用vue的好处:1、Vue是组件化开发,减少代码的书写,使代码易于理解;2、可以对数据进行双向绑定;3

    Vue中的路由懒加载

    Vue中的路由懒加载,组件,路由,应用程序,懒加载,导入,函数,随着Web应用程序的复杂性不断增加,前端框架和库的使用也越来越广泛。Vue是一种流行的J

    vue路由模式有哪些

    vue路由模式有哪些,模式,浏览器,路由,请求,刷新,服务器,vue路由模式有:1、hash模式,使用URL的hash值来作为路由,支持所有浏览器;其url路径会出现“#

    如何封装组件vue

    如何封装组件vue,组件,函数,封装,复用,组件开发,维护,Vue 是一种流行的 JavaScript 框架,它可以帮助开发者快速构建交互式的 Web 应用。Vue 的一

    vue如何实现页面跳转

    vue如何实现页面跳转,页面跳转,新窗口,方法,标签,属性,函数,vue实现页面跳转的方法:1、通过<vue-link>标签实现新窗口打开;2、通过在单击事件或者