Vue圖片裁剪功能實(shí)現(xiàn)代碼
一、效果展示:
1、表單的圖片上傳項(xiàng):
- 新增時(shí)默認(rèn)一個(gè)空白Input框
- 更新時(shí)展示以往上傳存放的圖片,
- 點(diǎn)擊【查看】瀏覽完整大小
- 點(diǎn)擊【刪除】清空src地址,重新上傳新照片
2、裁剪框頁(yè)面
- 先選擇裁剪的圖片
- 右側(cè)展示裁剪區(qū)域
- 支持放大縮小,圖片旋轉(zhuǎn)
- 點(diǎn)擊【上傳圖片】調(diào)用后臺(tái)上傳接口進(jìn)行上傳
二、代碼部分
1、首先安裝Vue-Cropper,基于此組件的基礎(chǔ)上開發(fā)的裁剪頁(yè)面
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" >? 左旋轉(zhuǎn)</el-button> <el-button size="mini" type="danger" plain @click="rotateRight" >? 右旋轉(zhuǎn)</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> <!--預(yù)覽效果圖--> <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: { // 默認(rèn)生成截圖框?qū)挾? type: Number, default: 410 }, autoCropHeight: { // 默認(rèn)生成截圖框高度 type: Number, default: 150 }, busType: { type: String, default: 'advertPic' } }, data() { return { loading: false, name: this.Name, previews: {}, option: { img: '', // 裁剪圖片的地址 outputSize: 1, // 裁剪生成圖片的質(zhì)量(可選0.1 - 1) outputType: 'jpeg', // 裁剪生成圖片的格式(jpeg || png || webp) info: true, // 圖片大小信息 canScale: true, // 圖片是否允許滾輪縮放 autoCrop: true, // 是否默認(rèn)生成截圖框 // autoCropWidth: 410, 默認(rèn)生成截圖框?qū)挾? // autoCropHeight: 150, 默認(rèn)生成截圖框高度 fixed: false, // 是否開啟截圖框?qū)捀吖潭ū壤? fixedNumber: [1.53, 1], // 截圖框的寬高比例 full: true, // false按原比例裁切圖片,不失真 fixedBox: true, // 固定截圖框大小,不允許改變 canMove: true, // 上傳圖片是否可以移動(dòng) canMoveBox: true, // 截圖框能否拖動(dòng) original: true, // 上傳圖片按照原始比例渲染 centerBox: false, // 截圖框是否被限制在圖片里面 height: true, // 是否按照設(shè)備的dpr 輸出等比例圖片 infoTrue: false, // true為展示真實(shí)輸出圖片寬高,false展示看到的截圖框?qū)捀? maxImgSize: 3000, // 限制圖片最大寬度和高度 enlarge: 1, // 圖片根據(jù)截圖框輸出比例倍數(shù) mode: '230px 150px' // 圖片默認(rèn)渲染方式 }, randomFileName: '' } }, methods: { // 初始化函數(shù) imgLoad(msg) { console.log('工具初始化函數(shù)=====' + msg) }, // 圖片縮放 changeScale(num) { num = num || 1 this.$refs.cropper.changeScale(num) }, // 向左旋轉(zhuǎn) rotateLeft() { this.$refs.cropper.rotateLeft() }, // 向右旋轉(zhuǎn) rotateRight() { this.$refs.cropper.rotateRight() }, // 實(shí)時(shí)預(yù)覽函數(shù) 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 } // 轉(zhuǎn)化為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}`) // 轉(zhuǎn)化為base64 reader.readAsDataURL(file) }, // 上傳圖片 uploadImg(type) { const _this = this if (type === 'blob') { // 獲取截圖的blob數(shù)據(jù) 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對(duì)象的filename屬性賦值文件名 formData.append('rpc', data, _this.randomFileName) // 給參數(shù)賦值文件名 formData.append('fileName', _this.randomFileName) formData.append('busType', _this.busType) /* this.fileName = data.file.name formData.append('fileName', this.fileName)*/ // 調(diào)用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.jpg', // url: res.data.agentUrl, // storeUrl: res.data.storeUrl, // uploadResult: res.data.uploadResult // } // _this.$emit('uploadImgSuccess', imgInfo) // 添加隨機(jī)生成的文件名 res.fileName = _this.randomFileName _this.$emit('uploadImgSuccess', res) } else { _this.$message({ message: '文件服務(wù)異常,請(qǐng)聯(lián)系管理員!', type: 'error' }) } }).finally(() => { _this.loading = false }) }) /* if (flag) { this.$message.warning('請(qǐng)選擇圖片') }*/ } }, createNewFileName() { // const now = Date.now() // const fileName = now + '-' + Math.ceil(Math.random() * 100) // return fileName + '.jpg' const fileName = regularFileName() return fileName + '.jpg' } } } </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'
后臺(tái)接口參數(shù)如下,要求表單方式上傳
/** * 上傳附件 * * @param file 文件流(注意帶文件后綴,統(tǒng)一使用.jpg結(jié)尾) * @param fileName 文件名稱(唯一性) * @param busType 業(yè)務(wù)類型(具體值參考ApiConstants類中FILE_開頭常量說(shuō)明) * @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函數(shù)這里,使用FormData對(duì)象包裝請(qǐng)求參數(shù)
注意append方法,要給文件對(duì)象指定文件名,必須要入?yún)⒌谌齻€(gè)參數(shù)
否則默認(rèn)名稱blob
按實(shí)際接口對(duì)應(yīng)調(diào)整參數(shù)即可
const formData = new FormData() _this.randomFileName = this.createNewFileName() // 給blob對(duì)象的filename屬性賦值文件名 formData.append('rpc', data, _this.randomFileName) // 給參數(shù)賦值文件名 formData.append('fileName', _this.randomFileName) formData.append('busType', _this.busType) uploadFile(formData)
其它自定義參數(shù),通過(guò)Props屬性傳入此組件
props: { autoCropWidth: { // 默認(rèn)生成截圖框?qū)挾? type: Number, default: 410 }, autoCropHeight: { // 默認(rèn)生成截圖框高度 type: Number, default: 150 }, busType: { type: String, default: 'advertPic' } },
文件名的生成方法,就是當(dāng)前時(shí)間按單位數(shù)值排序
實(shí)際使用根據(jù)業(yè)務(wù)實(shí)際情況改寫
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、【圖片上傳表單項(xiàng)】組件編寫
<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>封面設(shè)置</span>--> </div> </div> <input v-model="formValidate.mainImage" type="hidden" placeholder="請(qǐng)?zhí)砑臃饷? > </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: { // 默認(rèn)生成截圖框?qū)挾? type: Number, default: 410 }, autoCropHeight: { // 默認(rèn)生成截圖框高度 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('請(qǐng)輸上傳圖片')) } return callback() } return { formValidate: { mainImage: '' }, ruleValidate: { mainImage: [ /* { required: true, message: '請(qǐng)上傳圖片', trigger: 'blur' }*/ { required: true, validator: imageUrl2, trigger: 'blur' } ] }, // 裁切圖片參數(shù) 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) }) }, // 封面設(shè)置 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>
表單項(xiàng)組件需要引入
1、裁剪組件
2、圖片下載接口
import CropperImage from '@/components/CropperImage' import { commonsDownloadAPI } from '@/api/smrz/setting'
3、表單項(xiàng)設(shè)置了自定義校驗(yàn)
var imageUrl2 = (rule, value, callback) => { if (!this.isSignFlag) { return callback() } if (!value) { return callback(new Error('請(qǐng)輸上傳圖片')) } return callback() }
就是檢查src有沒有地址或者base64資源,校驗(yàn)觸發(fā)的效果:
4、圖片上傳后的回調(diào)處理:
上傳成功后,回到表單頁(yè)需要立即回顯之前上傳的圖片
所以需要調(diào)用圖片下載接口來(lái)獲取剛剛上傳的資源,
在這個(gè)回調(diào)方法中實(shí)現(xiàn),因?yàn)橄螺d接口提供的資源不是圖片地址,而是返回Base64編碼
這里我寫的是base64編碼資源的回顯處理
實(shí)際使用根據(jù)業(yè)務(wù)實(shí)際情況改寫
// 圖片上傳成功后 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、業(yè)務(wù)功能引用
引入表單項(xiàng)
import Tailoring from '@/components/Tailoring'
聲明組件,并注入?yún)?shù)
<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變量用來(lái)輔助自定義校驗(yàn)的,為false時(shí)直接放行校驗(yàn),所以默認(rèn)寫死true
- bus-type是自定義的業(yè)務(wù)參數(shù)
- auto-crop的寬高用來(lái)配置裁剪的寬高,預(yù)覽大小和裁剪大小合并使用這兩個(gè)參數(shù)
上傳成功的回調(diào),uploadSuccess,可以在組件自定義需要的參數(shù)
這里是以圖片名稱作為記錄主鍵,所以要傳入這個(gè)文件名
實(shí)際使用根據(jù)業(yè)務(wù)實(shí)際情況改寫
async uploadSuccess(res) { console.log(`上傳結(jié)果 res -> ${JSON.stringify(res)}`) const fileName = res.fileName this.newId = fileName.substring(0, fileName.lastIndexOf('.')) },
校驗(yàn)值,應(yīng)該是返回校驗(yàn)后的src值,但我這里沒用上,所以不執(zhí)行任何邏輯
validVal(val) {},
要觸發(fā)【裁剪表單項(xiàng)】校驗(yàn),使用
this.$refs.child.validateForm()
到此這篇關(guān)于Vue圖片裁剪功能支持的文章就介紹到這了,更多相關(guān)vue圖片裁剪內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
electron-vue利用webpack打包實(shí)現(xiàn)多頁(yè)面的入口文件問題
項(xiàng)目需要在electron的項(xiàng)目中新打開一個(gè)窗口,利用webpack作為靜態(tài)資源打包器,發(fā)現(xiàn)在webpack中可以設(shè)置多頁(yè)面的入口,今天來(lái)講一下我在electron中利用webpack建立多頁(yè)面入口的踩坑經(jīng)驗(yàn),需要的朋友可以參考下2019-05-05基于Vue中點(diǎn)擊組件外關(guān)閉組件的實(shí)現(xiàn)方法
下面小編就為大家分享一篇基于Vue中點(diǎn)擊組件外關(guān)閉組件的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03vue 使用 sortable 實(shí)現(xiàn) el-table 拖拽排序功能
這篇文章主要介紹了vue 使用 sortable 實(shí)現(xiàn) el-table 拖拽排序功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12vue+element 模態(tài)框表格形式的可編輯表單實(shí)現(xiàn)
這篇文章主要介紹了vue+element 模態(tài)框表格形式的可編輯表單實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-06-06elementui的table列超出隱藏tooltip懸浮顯示問題
這篇文章主要介紹了elementui的table列超出隱藏tooltip懸浮顯示問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Vue前端導(dǎo)出Excel文件的詳細(xì)實(shí)現(xiàn)方案
在開發(fā)后臺(tái)管理系統(tǒng)的時(shí)候,很多地方都要用到導(dǎo)出excel表格,比如將table中的數(shù)據(jù)導(dǎo)出到本地,下面這篇文章主要給大家介紹了關(guān)于Vue導(dǎo)出Excel文件的相關(guān)資料,需要的朋友可以參考下2021-09-09Vue?關(guān)于$emit與props的使用示例代碼
父組件使用 props 把數(shù)據(jù)傳給子組件,子組件使用 $emit 觸發(fā)父組件的自定義事件,今天通過(guò)示例給大家詳細(xì)介紹下Vue?關(guān)于$emit與props的使用,感興趣的朋友一起看看吧2022-03-03vue使用axios時(shí)關(guān)于this的指向問題詳解
最近在學(xué)習(xí)使用vue+axios,在使用中發(fā)現(xiàn)了一個(gè)問題,下面總結(jié)分享給大家,這篇文章主要給大家介紹了關(guān)于vue使用axios時(shí)this的指向問題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2017-12-12