1.vue3+element-plus+vue-cropper实现裁剪图片
element-UI官网 element-plus官网 vue-cropper vue3使用vue-cropper安装:npm install vue-cropper@next
2.vue-cropper插件:
< vue-cropper :img = " option.img" /> < script setup > import { reactive} from "vue" ; const option = reactive ( { img : 'https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500' } ) </ script>
3.效果图:
4.实现cropperUpload组件:
< template> < div class = " uploadMian" > < div class = " img-item" v-for = " (item, index) in fileList" :key = " index" > < img :src = " item.src" /> < el-icon class = " uploader-close" @click = " delFn(index)" > < Close /> </ el-icon> < div v-if = " item.isSuccess" class = " uploader-Check" > < el-icon > < Check /> </ el-icon> </ div> < div class = " button-div" v-if = " item.file && isCropper" > < el-button type = " success" @click = " uploadFileFn(item, index)" > 上传</ el-button> < el-button type = " primary" @click = " cropperFn(item, index)" > 裁剪</ el-button> </ div> </ div> < el-uploadv-if = " multiple || (!multiple && fileList.length == 0)" class = " avatar-uploader" action = " #" :accept = " acceptArray.length > 0? acceptArray.map((n) => acceptType[n]).join(','): '*'" :http-request = " !isCropper ? uploadFileFn : () => {}" :multiple = " multiple" :show-file-list = " false" :before-upload = " beforeAvatarUpload" > < el-icon class = " avatar-uploader-icon" > < Plus /> </ el-icon> </ el-upload> </ div> < el-dialog title = " 裁切图片" v-model = " showCropper" width = " 550px" > < div class = " cropper-content" > < div class = " cropper-box" > < div class = " cropper" > < vue-cropperref = " cropperRefs" :img = " option.img" :output-size = " option.outputSize" :info = " option.info" :can-scale = " option.canScale" :auto-crop = " option.autoCrop" :auto-crop-width = " option.autoCropWidth" :auto-crop-height = " option.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" :limit-min-size = " option.limitMinSize" /> </ div> </ div> </ div> < span slot = " footer" > < div class = " dialog-footer" > < el-button @click = " showCropper = false" > 取 消</ el-button> < el-button type = " primary" @click = " onSubmit" > 确 定</ el-button> </ div> </ span> </ el-dialog>
</ template> < script setup >
import { ref, reactive, watch } from "vue" ;
import { Plus, Close, Check } from "@element-plus/icons-vue" ;
import "vue-cropper/dist/index.css" ;
import { VueCropper } from "vue-cropper" ;
const props = defineProps ( { otherData : { type : Object, default : ( ) => { } , } , headers : { type : Object, default : ( ) => { } , } , modelValue : { type : Array, default : ( ) => { return [ ] ; } , } , multiple : { type : Boolean, default : false , } , size : { type : Number, default : 10 * 1024 * 1024 , } , isCropper : { type : Boolean, default : true , } , sendUrl : { type : String, default : "" , } ,
} ) ;
const emits = defineEmits ( [ "update:modelValue" ] ) ;
const cropperRefs = ref ( ) ;
const cropperCb = ref ( null ) ;
const showCropper = ref ( false ) ;
let fileList = reactive ( [ ] ) ;
const acceptArray = reactive ( [ "png" , "jpg" , "jpeg" ] ) ;
const acceptType = reactive ( { doc : "application/msword" , docx : "application/vnd.openxmlformats-officedocument.wordprocessingml.document" , ppt : "application/vnd.ms-powerpoint" , pptx : "application/vnd.openxmlformats-officedocument.presentationml.presentation" , xls : "application/vnd.ms-excel" , xlsx : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" , pdf : "application/pdf" , csv : ".csv" , txt : "text/plain" , image : "image/*" , png : "image/png" , gif : "image/gif" , jpg : "image/jpg" , jpeg : "image/jpeg" ,
} ) ;
watch ( props. modelValue, ( value ) => { const valueList = value || [ ] ; let newFileList = [ ] ; valueList. forEach ( ( item ) => { const indexThis= fileList. findIndex ( n => n. src== item) if ( indexThis== - 1 ) { newFileList. push ( { src : item, isSuccess : true , } ) ; } } ) ; fileList. unshift ( ... newFileList) ; } , { immediate : true , deep : true }
) ;
watch ( fileList, ( value ) => { const valueList = value. map ( ( n ) => { if ( n. isSuccess) { return n. src; } return null ; } ) . filter ( ( n ) => n != null ) ; emits ( "update:modelValue" , valueList) ; } , { deep : true }
) ;
const option = reactive ( { img : "" , outputSize : 1 , outputType : "jpeg" , info : false , canScale : true , autoCrop : true , autoCropWidth : 230 , autoCropHeight : 150 , fixed : false , fixedNumber : [ 1.53 , 1 ] , full : false , fixedBox : false , canMove : true , canMoveBox : true , original : true , centerBox : true , high : false , infoTrue : false , maxImgSize : 3000 , enlarge : 1 , mode : "550px 400px" , limitMinSize : [ 108 , 108 ] , minCropBoxWidth : 108 , minCropBoxHeight : 108 ,
} ) ;
const judegFileSize = ( file ) => { const filterSize = ( size ) => { const pow1024 = ( num ) => { return Math. pow ( 1024 , num) ; } ; if ( ! size) return "" ; if ( size < pow1024 ( 1 ) ) return size + " B" ; if ( size < pow1024 ( 2 ) ) return ( size / pow1024 ( 1 ) ) . toFixed ( 0 ) + " KB" ; if ( size < pow1024 ( 3 ) ) return ( size / pow1024 ( 2 ) ) . toFixed ( 0 ) + " MB" ; if ( size < pow1024 ( 4 ) ) return ( size / pow1024 ( 3 ) ) . toFixed ( 0 ) + " GB" ; return ( size / pow1024 ( 4 ) ) . toFixed ( 2 ) + " TB" ; } ; let retunBoolean = true ; let fileSize = file. size; const fileExtArray = file. name. split ( "." ) ; const judegFn = ( ) => { if ( acceptArray. indexOf ( fileExtArray. at ( - 1 ) ) == - 1 ) { alert ( ` ${ file. name} 上传失败,只能上传 ${ acceptArray. join ( "、" ) } ` ) ; retunBoolean = false ; } } ; if ( acceptArray. length > 0 ) { if ( acceptArray. indexOf ( "image" ) != - 1 ) { var pattern = / (\.jpg|\.jpeg|\.png|\.gif)$ / i ; if ( ! pattern. test ( ` . ${ fileExtArray. at ( - 1 ) } ` ) ) { judegFn ( ) ; } } else { judegFn ( ) ; } } if ( retunBoolean) { if ( props. size > 0 && fileSize > props. size) { alert ( ` 最大上传 ${ filterSize ( props. size) } ` ) ; retunBoolean = false ; } } return retunBoolean;
} ;
const beforeAvatarUpload = ( rawFile ) => { let retunBoolean = judegFileSize ( rawFile) ; if ( retunBoolean) { fileList. push ( { src : URL . createObjectURL ( rawFile) , file : rawFile, } ) ; } return retunBoolean;
} ;
const cropperFn = ( item, index ) => { showCropper. value = true ; option. img = URL . createObjectURL ( item. file) ; const reader = new FileReader ( ) ; reader. readAsDataURL ( item. file) ; cropperCb. value = ( res ) => { if ( res) { cropperRefs. value. getCropBlob ( ( data ) => { const result = new File ( [ data] , item. file. name, { type : item. file. type, lastModified : Date. now ( ) , } ) ; result[ "uid" ] = item. file. uid; fileList. splice ( index, 1 , { src : URL . createObjectURL ( result) , file : result, } ) ; showCropper. value = false ; } ) ; } } ;
} ;
const delFn = ( index ) => { fileList. splice ( index, 1 ) ;
} ;
const onSubmit = ( ) => { if ( cropperCb. value) cropperCb. value ( true ) ;
} ;
const uploadFileFn = ( item ) => { if ( props. sendUrl == "" ) return false ; const successFn = ( url ) => { const index = fileList. findIndex ( ( n ) => { if ( n. file && n. file. uid == item. file. uid) { return true ; } return false ; } ) ; if ( index != - 1 ) { fileList. splice ( index, 1 , { src : url, file : item. file, isSuccess : true , } ) ; } } ; const formData = new FormData ( ) ; formData. append ( "file" , item. file) ; if ( props. otherData) { Object. keys ( props. otherData) . forEach ( ( key ) => { formData. append ( key, props. otherData[ key] ) ; } ) ; } fetch ( props. sendUrl, { method : "POST" , body : formData, headers : props. headers, "Content-type" : "multipart/form-data" , } ) . then ( ( respone ) => respone. json ( ) ) . then ( ( res ) => { successFn ( "成功的url" ) ; } ) . catch ( ( error ) => { } ) ;
} ;
</ script>
< style scoped lang = " scss" >
.el-icon.avatar-uploader-icon { font-size : 28px; color : #8c939d; border : 1px solid #ccc; width : 178px; height : 178px; text-align : center;
} .uploadMian { vertical-align : top; display : flex; flex-wrap : wrap;
}
.avatar-uploader {
} .img-item { display : inline-block; width : 178px; height : 178px; margin-right : 10px; border : 1px solid #ccc; position : relative; img { width : 100%; height : 100%; object-fit : contain; position : relative; z-index : 9; } &:hover { .el-icon.uploader-close { display : flex !important ; } } .uploader-Check { width : 40px; height : 40px; position : absolute; z-index : 18; top : 0; left : 0; display : flex; background-color : #67c23a; clip-path : polygon ( 0 0 , 100% 0, 0 100%) ; -webkit-clip-path : polygon ( 0 0 , 100% 0, 0 100% ) ; .el-icon { position : absolute; top : 4px; left : 4px; color : #fff; } } .el-icon.uploader-close { display : none; position : absolute; z-index : 20; top : -5px; right : -5px; width : 20px; height : 20px; background-color : red; justify-content : center; align-items : center; border-radius : 50%; color : #fff; font-size : 12px; cursor : pointer; } .button-div { position : absolute; height : 45px; z-index : 20; bottom : 0; left : 0; width : 100%; background-color : rgba ( 0, 0, 0, 0.2) ; display : flex; justify-content : space-around; align-items : center; }
}
.cropper-content { display : flex; display : -webkit-flex; justify-content : flex-end; .cropper-box { width : 550px; .cropper { width : auto; height : 400px; } } .show-preview { flex : 1; -webkit-flex : 1; display : flex; display : -webkit-flex; justify-content : center; .preview { overflow : hidden; border : 1px solid #67c23a; background : #cccccc; } }
}
.dialog-footer { display : flex; justify-content : center; margin-top : 10px;
}
</ style>
5.使用:
< cropperUpload :otherData = " {a:100}" :headers = " {}" v-model = " urlList" :multiple = " true" sendUrl = " https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" /> < script setup > import cropperUpload from "./cropperUpload.vue" ; const urlList = reactive ( [ 'https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500' ] ) </ script>