VUE 图片上传/剪切(vue-cropper)

vue

1. 概述

1.1 说明

  项目中为了保证图片展示效果以及分辨率高度匹配,需对图片的尺寸、大小、类型等进行控制;最大限度保证图片在网站、小程序、app端的展示效果保持一致。

1.2 思路

  使用vue-cropper进行图片裁剪功能,使用iview组件Upload进行图片上传

  1.2.1 功能选择

    使用下拉框进行功能选择,如图片直接上传、图片剪裁等

    

  1.2.2 图片上传与展现

    使用Upload进行图片上传,根据所需上传图片的大小、格式等进行图片验证;所有验证通过后再进行图片上传操作。

    

  1.2.3 最终组件样式

   

  1.2.4 组件使用样式

   

  1.3 vue-cropper使用

    1.  安装  npm install vue-cropper 或者 yarn add vue-cropper
    2.  main.js引用注入 

      1.   import VueCropper from \'vue-cropper\'
      2.   Vue.use(VueCropper)
    3.  页面使用  <vueCropper></vueCropper>

  1.4 图片规则

2. 代码

2.1 示例代码结构:

2.2 图片上传、剪裁、展现公用组件(common)

2.2.1  图片剪裁组件代码(imageCropper.vue)

<!-- 图片上传剪裁 -->

<template>

<div ref="refCommonImageCropper" class="image-cropper-wrapper">

<div class="cropper-content">

<div class="operate-content">

<div class="scope-btn">

<label class="btn" for="uploads">{{ options.imgUrl ? \'更换图片\' : \'上传图片\'}}</label>

<input

type="file"

id="uploads"

style="position:absolute; clip:rect(0 0 0 0);"

accept="image/png, image/jpeg, image/gif, image/jpg"

@change="uploadImg($event)"

/>

<Button @click="changeScale(1)" title="放大">+</Button>

<Button @click="changeScale(-1)" title="缩小">-</Button>

<Button @click="rotateLeft" title="左旋转">↺</Button>

<Button @click="rotateRight" title="右旋转">↻</Button>

<!-- <Button @click="imageCropperSubmit(\'blob\')">确定</Button> -->

</div>

</div>

<div :style="{\'width\': options.imgWidth + \'px\', \'height\': options.imgHeight + \'px\'}">

<vueCropper

ref="cropper"

:img="options.imgUrl"

:outputSize="options.size"

:outputType="option.outputType"

:info="true"

:full="option.full"

:canMove="option.canMove"

:canMoveBox="option.canMoveBox"

:original="option.original"

:autoCrop="option.autoCrop"

:autoCropWidth="options.cropWidth"

:autoCropHeight="options.cropHeight"

:fixedBox="option.fixedBox"

@realTime="realTime"

@imgLoad="imgLoad"

></vueCropper>

</div>

</div>

<div class="cropper-operate" v-if="isShowView">

<div style="height:42px;padding:6px;">剪裁图片预览</div>

<div

class="show-preview"

:style="{\'width\': options.cropWidth + \'px\', \'height\': options.cropHeight + \'px\', \'overflow\': \'hidden\', \'margin\': \'5px\'}"

>

<div :style="previews.div" class="preview">

<img :src="previews.url" :style="previews.img" />

</div>

</div>

</div>

</div>

</template>

<script>

import { uploadImage } from \'../../../api/account.js\'

export default {

name: \'image-cropper\',

props: {

/**

* 裁剪配置

*/

setting: {

type: Object,

default () {

return {

imgUrl: \'\',

cropWidth: 200,

cropHeight: 200,

imgWidth: 350,

imgHeight: 300,

size: 1

}

}

},

/**

* 预览区域是否展示

*/

isShowView: {

type: Boolean,

default: true

}

},

data () {

return {

crap: false,

previews: {},

option: {

full: false,

outputType: \'png\',

canMove: true,

original: false,

canMoveBox: true,

autoCrop: true,

fixedBox: true

},

fileName: \'\'

}

},

computed: {

options () {

return this.setting

}

},

watch: {

options: {

// eslint-disable-next-line

handler (newVal, oldVal) {

this.setting = newVal

},

deep: true

}

},

methods: {

/**

* 更改比例

*/

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

},

/**

* 被剪裁图片的上传以及更改

*/

uploadImg (e) {

// 上传图片

var file = e.target.files[0]

if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {

this.$Message.warning(\'图片类型必须是jpeg,jpg,png中的一种\')

return false

}

this.fileName = file.name

var reader = new FileReader()

let type = file.type

reader.onload = (e) => {

let data

if (typeof e.target.result === \'object\') {

// 把Array Buffer转化为blob 如果是base64不需要

data = URL.createObjectURL(new Blob([e.target.result], { type }))

} else {

data = e.target.result

}

this.options.imgUrl = data

}

// 转化为base64

reader.readAsDataURL(file)

// 转化为blob

// reader.readAsArrayBuffer(file)

},

imgLoad (msg) {

console.log(msg)

},

/**

* 剪裁图片上传

*/

imageCropperSubmit (type) {

if (this.options.imgUrl) {

if (type === \'blob\') {

this.$refs.cropper.getCropBlob(data => {

let formData = new FormData()

formData.append(\'file\', data, this.fileName)

uploadImage(formData).then(data => {

this.$Message.success(\'上传成功\')

let cropperData = {

httpOriginalFileUri: data.httpOriginalFileUri,

originalFileUri: data.originalFileUri,

converFileUri: data.converFileUri,

fileName: this.fileName

}

this.$emit(\'fileSuccess\', cropperData)

}).catch(err => {

console.log(err)

})

})

} else {

// 此处得到的是base64位图片数据,暂无用

this.$refs.cropper.getCropData(data => {

console.log(data)

})

}

} else {

this.$Message.warning(\'请上传需裁剪图片\')

}

}

}

}

</script>

<style lang="scss" scoped>

.image-cropper-wrapper{

display: flex;

flex-direction: row;

justify-content: center;

.cropper-content {

display: flex;

display: -webkit-flex;

flex-direction: column;

.operate-content {

margin-bottom: 10px;

display: flex;

display: -webkit-flex;

.scope-btn {

display: flex;

display: -webkit-flex;

>button {

margin-left: 6px;

}

}

.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;

margin: 0;

-webkit-transition: 0.1s;

transition: 0.1s;

font-weight: 500;

padding: 8px 15px;

font-size: 12px;

border-radius: 3px;

color: #fff;

background-color: #67c23a;

border-color: #67c23a;

}

}

}

.cropper-operate{

display: flex;

flex-direction: column;

padding-left: 20px;

}

.show-preview {

flex: 1;

-webkit-flex: 1;

display: flex;

display: -webkit-flex;

justify-content: center;

align-items: center;

-webkit-justify-content: center;

.preview {

overflow: hidden;

border: 1px solid #cccccc;

background: #cccccc;

}

}

}

</style>

2.2.1  图片上传、展现、裁剪组件代码(imageOperate.vue)

<!-- 图片上传 -->

<template>

<div class="image-upload-wrapper">

<div class="default-upload-list" v-if="formInfo.fileUrl">

<img :src="formInfo.fileUrl">

<div class="default-upload-list-cover">

<Icon type="ios-eye-outline" @click.native="avatarModal=true"></Icon>

<Icon v-if="!isDisable" type="ios-trash-outline" @click.native="handleFileRemove"></Icon>

</div>

</div>

<div v-else class="image-upload-item">

<Select

v-model="formInfo.uploadTypeSelected"

placeholder="请选择图片上传方式"

:disabled="`${imgHeight}`==\'0\'"

:title="`${imgHeight}`==\'0\'?\'尺寸不限时只能进行直接上传操作\':\'\'"

@on-change="onChangeImageUploadType">

<Option

v-for="item in imageUploadType"

:value="item.value"

:key="item.value"

>{{ item.label }}</Option>

</Select>

<div class="image-upload-content">

<div v-if="formInfo.uploadTypeSelected==\'cropperUpload\'" class="cut-img-wrapper">

<div style="width: 58px;height:58px;line-height: 58px;" @click="uploadImage">

<Icon

type="ios-cut"

size="20"

style="color: #3399ff"

></Icon>

</div>

</div>

<!-- 直接上传 -->

<Upload

v-else

ref="refWholeUpload"

type="drag"

action="/api/admin/attach/uploadAttach"

style="width: 58px;height:58px;"

:show-upload-list="false"

:on-success="fileSuccess"

:before-upload="handleBeforeUpload"

:on-progress="uploadProgress"

:on-error="uploaderror">

<div style="width: 58px;height:58px;line-height: 58px;">

<Icon

type="ios-cloud-upload"

size="20"

style="color: #3399ff"

></Icon>

</div>

</Upload>

<Alert

v-if="!formInfo.fileUrl"

:type="imageError?\'error\':\'info\'"

class="image-info">

<div>宽高[{{(`${imgWidth}`!=\'0\'?imgWidth:\'不限\')}}*{{(`${imgHeight}`!=\'0\'?imgHeight:\'不限\')}}]&nbsp;&nbsp;格式[{{formInfo.uploadTypeSelected==\'IconUpload\'?\'.ico\':\'.png/.jpg/.jpeg\'}}]&nbsp;&nbsp;最大[{{imgSizeTip}}]</div>

<div v-if="imageError" style="color:red;">{{imageError}}</div>

</Alert>

</div>

</div>

<Modal

v-model="avatarModal"

:mask-closable="false"

:closable=\'false\'

:z-index=\'1001\'>

<img :src="formInfo.fileUrl" style="width: 100%">

<div slot="footer">

<Button

type="primary"

@click="avatarModal = false"

>关闭</Button>

</div>

</Modal>

<Modal

:width="modalCropperWidth"

v-model="modalImageCropper"

title=\'图片剪切处理\'

:mask-closable="false"

:z-index=\'2001\'

class-name="vertical-center-modal"

draggable>

<image-cropper

ref="refImageCropper"

@fileSuccess="imageCropperSuccess"

:setting="setOptions"

:isShowView="isShowViewImg"

v-if="modalImageCropper" />

<div slot="footer">

<Button @click="modalImageCropper = false">取消</Button>

<Button type="primary" @click="submitImageCropper">确定</Button>

</div>

</Modal>

<Spin

fix

style="z-index:9999"

v-if=\'isLoading\'

class="spin"

>图片上传中...</Spin>

</div>

</template>

<script>

import imageCropper from \'./imageCropper\'

export default {

name: \'image-operate\',

props: {

// 图片路径

imageUrl: {

type: String,

default () {

return \'\'

}

},

// 图片大小

imageSize: {

type: Number,

default () {

return 500

}

},

// 图片尺寸——宽

imgWidth: {

type: String | Number,

default () {

return 100

}

},

// 图片尺寸——高

imgHeight: {

type: String | Number,

default () {

return 100

}

},

// 图片大小文字内容

imgSizeTip: {

type: String,

default () {

return \'500KB\'

}

},

// 是否启用

isDisable: {

type: Boolean,

default: false

},

// 是否支持icon直接上传

isIconUpload: {

type: Boolean,

default: false

}

},

components: {

\'image-cropper\': imageCropper

},

data () {

return {

isLoading: false,

// 图片上传类型选择

imageUploadType: [],

formInfo: {

uploadTypeSelected: \'wholeUpload\',

fileUrl: \'\',

submitUrl: \'\'

},

// 图片预览

avatarModal: false,

attach: {

fileName: \'\',

originalFileUri: \'\',

converFileUri: \'\'

},

// 图片直接上传错误

imageError: \'\',

// 图片裁剪

setOptions: {

imgUrl: \'\',

size: 1,

cropWidth: 200,

cropHeight: 200,

imgWidth: 350,

imgHeight: 300

},

isShowViewImg: true,

modalCropperWidth: 700,

modalImageCropper: false

}

},

mounted () {

if (this.isIconUpload) {

this.imageUploadType = [

{ label: \'图片直接上传\', value: \'wholeUpload\' },

{ label: \'图片剪裁上传\', value: \'cropperUpload\' },

{ label: \'ICON图片直接上传\', value: \'IconUpload\' }

]

} else {

this.imageUploadType = [

{ label: \'图片直接上传\', value: \'wholeUpload\' },

{ label: \'图片剪裁上传\', value: \'cropperUpload\' }

]

}

this.imageError = \'\'

this.formInfo.fileUrl = this.imageUrl

},

methods: {

/**

* 图片上传类型选择

*/

onChangeImageUploadType (val) {

this.imageError = \'\'

this.handleFileRemove()

},

/**

* 裁剪图片

*/

uploadImage () {

this.setOptions.imgUrl = \'\'

this.setOptions.size = 1

this.setOptions.cropWidth = this.imgWidth

this.setOptions.cropHeight = this.imgHeight

this.setOptions.imgWidth = this.imgWidth + 20

this.setOptions.imgHeight = this.imgHeight + 20

let screenWidth = document.body.clientWidth

let maxWidth = screenWidth / 2 - 100

if (maxWidth >= this.imgWidth + 50) {

this.isShowViewImg = true

this.modalCropperWidth = (this.imgWidth + 40) * 2 > 500 ? (this.imgWidth + 50) * 2 : 500

} else {

this.isShowViewImg = false

if ((this.imgWidth + 50) * 2 <= 500) {

this.modalCropperWidth = 500

} else {

this.modalCropperWidth = this.imgWidth + 100

}

}

this.modalImageCropper = true

},

/**

* 图片确定上传

*/

submitImageCropper () {

this.$refs.refImageCropper.imageCropperSubmit(\'blob\')

},

/**

* 图片上传成功后返回对应图片信息

*/

imageCropperSuccess (val) {

this.formInfo.fileUrl = val.httpOriginalFileUri

this.formInfo.submitUrl = val.httpOriginalFileUri

const data = {

fileName: val.fileName,

originalFileUri: val.originalFileUri,

converFileUri: val.converFileUri,

fileUrl: val.httpOriginalFileUri,

submitUrl: val.originalFileUri

}

this.$emit(\'imageUploadEmit\', data)

this.modalImageCropper = false

},

/**

* 删除图片

*/

handleFileRemove () {

this.attach = {

fileName: this.attach.fileName,

originalFileUri: \'\',

converFileUri: \'\'

}

this.formInfo.fileUrl = \'\'

this.formInfo.submitUrl = \'\'

const data = {

fileName: this.attach.fileName,

originalFileUri: \'\',

converFileUri: \'\',

fileUrl: \'\',

submitUrl: \'\'

}

this.$emit(\'imageUploadEmit\', data)

},

/**

* 附件上传成功返回值

*/

fileSuccess (res, file) {

if (res.result) {

const { httpOriginalFileUri, originalFileUri, converFileUri } = res.data

this.attach.fileName = file.name

this.attach.originalFileUri = originalFileUri

this.attach.converFileUri = converFileUri

this.formInfo.fileUrl = httpOriginalFileUri

this.formInfo.submitUrl = originalFileUri

const data = {

fileName: file.name,

originalFileUri: originalFileUri,

converFileUri: converFileUri,

fileUrl: httpOriginalFileUri,

submitUrl: originalFileUri

}

this.isLoading = false

this.$emit(\'imageUploadEmit\', data)

} else {

this.formInfo.fileUrl = \'\'

this.imageError = \'图片上传失败,请重新上传。\'

this.isLoading = false

}

},

uploadProgress (file) {

this.isLoading = true

},

/**

* 附件上传判断

*/

handleBeforeUpload (file) {

this.isLoading = true

this.imageError = \'\'

let check = this.$refs.refWholeUpload.fileList.length < 1

let isIcontype = file.type === \'image/x-icon\'

if (this.formInfo.uploadTypeSelected === \'IconUpload\') {

if (!isIcontype) {

this.imageError = \'请选择icon类型的图片上传。\'

this.isLoading = false

return false

}

}

if (!check) {

this.imageError = \'限制上传一张图片,请删除后重新上传。\'

this.isLoading = false

return false

} else {

return this.checkImageWH(file, this.imgWidth, this.imgHeight)

}

},

/**

* 判断上传文件类型

*/

judgeFileType (type) {

let typeList = [\'image/jpeg\', \'image/png\', \'image/jpg\', \'image/x-icon\']

let hasIndex = typeList.findIndex(item => item.indexOf(type) > -1)

if (hasIndex > -1) {

return true

} else return false

},

/**

* 返回一个promise 检测通过返回resolve 失败返回reject组织图片上传

*/

checkImageWH (file, width, height) {

let self = this

return new Promise(function (resolve, reject) {

let accordType = self.judgeFileType(file.type)

if (!accordType) {

self.imageError = \'上传的文件为非图片格式,请选择图片格式文件上传\'

self.isLoading = false

reject(new Error(self.imageError))

} else if (file.size / 1024 > self.imageSize) {

self.imageError = `上传图片不能超过${self.imgSizeTip}`

self.isLoading = false

reject(new Error(self.imageError))

} else {

if (`${width}` !== \'0\') {

let filereader = new FileReader()

filereader.onload = e => {

let src = e.target.result

const image = new Image()

image.onload = function () {

let errorInfo = \'\'

if ((width && this.width !== width)) {

errorInfo = `宽(${this.width})、`

}

if (height && `${height}` !== \'0\' && this.height !== height) {

errorInfo = `${errorInfo}高(${this.height})`

}

if (errorInfo) {

self.imageError = `上传图片错误:${errorInfo}`

self.isLoading = false

reject(new Error(self.imageError))

} else {

self.isLoading = false

resolve(true)

}

}

image.onerror = reject

image.src = src

}

filereader.readAsDataURL(file)

} else {

self.isLoading = false

resolve(true)

}

}

})

},

/**

* 附件上传失败

*/

uploaderror (file) {

this.isLoading = false

this.imageError = \'图片上传失败,请重新上传。\'

}

}

}

</script>

<style lang="scss" scoped>

.image-upload-wrapper .image-upload-item{

display:flex;

flex-direction: column;

}

.image-upload-wrapper .image-upload-content{

margin-top: 10px;

display: flex;

.image-info {

margin-left:10px;

text-align: left;

display:flex;

flex-direction:column;

justify-content:center;

padding: 0 10px;

font-size: 13px;

height: 60px;

}

}

.cut-img-wrapper{

background: #fff;

border: 1px dashed #dcdee2;

display: felx;

width: 58px;

height: 58px;

border-radius: 4px;

text-align: center;

cursor: pointer;

transition: border-color 0.2s;

}

.cut-img-wrapper:hover{

border: 1px dashed #2d8cf0;

}

.default-upload-list{

display: inline-block;

width: 60px;

height: 60px;

text-align: center;

line-height: 60px;

border: 1px solid transparent;

border-radius: 4px;

overflow: hidden;

background: #fff;

position: relative;

box-shadow: 0 1px 1px rgba(0,0,0,.2);

margin-right: 4px;

}

.default-upload-list img{

width: 100%;

height: 100%;

}

.default-upload-list-cover{

display: none;

position: absolute;

top: 0;

bottom: 0;

left: 0;

right: 0;

background: rgba(0,0,0,.6);

}

.default-upload-list:hover .default-upload-list-cover{

display: block;

}

.default-upload-list-cover i{

color: #fff;

font-size: 20px;

cursor: pointer;

margin: 0 2px;

}

</style>

2.3 使用示例(formOperate.vue/index.vue)

2.3.1  表单示例代码(formOperate.vue)

<template>

<Form ref="refForm" :model="formData" :rules="rules">

<FormItem label="图片" prop="fileUrl" label-for="fileUrl">

<Input v-show="false" v-model="formData.fileUrl" />

<image-upload

:imageUrl="formData.fileUrl"

:isIconUpload="true"

@imageUploadEmit="imageUploadFunction"

:imageSize="settingImageUpload.maxSize"

:imgWidth="settingImageUpload.width"

:imgHeight="settingImageUpload.height"

:imgSizeTip="settingImageUpload.sizeTip"

/>

</FormItem>

</Form>

</template>

<script>

import ImageUpload from "./common/imageOperate";

export default {

components: {

ImageUpload,

},

data() {

return {

formData: {

fileUrl: "", // 图片路径

},

settingImageUpload: {

width: 100,

height: 100,

maxSize: 100,

sizeTip: "100kb",

},

rules: {

fileUrl: { required: true, message: \'请上传图片\' }

}

};

},

methods: {

/**

* 图片上传成功

*/

imageUploadFunction(val) {

this.formData.originalFileUri = val.originalFileUri;

this.formData.obtainIconUrl = val.fileUrl;

}

},

};

</script>

2.3.2  入口示例代码(index.vue)

<template>

<div class="medo-wrapper">

<Button type="primary" @click="handleAdd">新建</Button>

<Drawer

:title="`${propOperateData.id ? \'修改\' : \'新增\'}表单`"

v-model="showInfoOperate"

:width="drawerStyles.width"

:styles="drawerStyles.style"

:mask-closable="false"

@on-close="showInfoOperate = false"

>

<form-operate

v-if="showInfoOperate"

:data="propOperateData"

ref="refFormOperate"

/>

<div class="default-drawer-footer">

<Button type="text" @click="showInfoOperate = false"> 关闭 </Button>

<Button type="primary" @click="handleSubmit"> 确定 </Button>

</div>

</Drawer>

</div>

</template>

<script>

import formOperate from \'./formOperate\'

export default {

components: {

formOperate

},

data() {

return {

showInfoOperate: false,

propOperateData: {

id: "",

fileUrl: "",

},

drawerStyles: {

// 内容样式

styles: {

height: \'calc(100% - 55px)\',

overflow: \'auto\',

paddingBottom: \'53px\',

position: \'static\'

},

width: 720

},

};

},

methods: {

handleAdd () {

this.showInfoOperate = true

},

/**

* 提交按钮

*/

handleSubmit() {

this.$refs.refFormOperate.$refs.refForm.validate(validate => {

if (validate) {

console.log(\'验证成功调用接口\')

}

})

}

}

};

</script>

<style lang="scss" scoped>

.default-drawer-footer{

width: 100%;

position: absolute;

bottom: 0;

left: 0;

border-top: 1px solid #e8e8e8;

padding: 10px 16px;

text-align: right;

background: #fff;

}

</style>

2.3 上传代码接口返回:

以上是 VUE 图片上传/剪切(vue-cropper) 的全部内容, 来源链接: utcz.com/z/380095.html

回到顶部