首先在你的node_modules文件里找到vue-image-crop-upload文件夹把upload-3.vue文件替换成下面的,复制全部替换即可
<template>
<div class="vue-image-crop-upload" v-show="show">
<div class="vicp-wrap">
<div class="vicp-close" @click="off">
<i class="vicp-icon4"></i>
</div>
<div class="vicp-step1" v-show="step == 1">
<div class="vicp-drop-area" @dragleave="preventDefault" @dragover="preventDefault" @dragenter="preventDefault" @click="handleClick" @drop="handleChange">
<i class="vicp-icon1" v-show="loading != 1">
<i class="vicp-icon1-arrow"></i>
<i class="vicp-icon1-body"></i>
<i class="vicp-icon1-bottom"></i>
</i>
<span class="vicp-hint" v-show="loading !== 1">{{ lang.hint }}</span>
<span class="vicp-no-supported-hint" v-show="!isSupported">{{ lang.noSupported }}</span>
<input type="file" accept="image/*" v-show="false" v-if="step == 1" @change="handleChange" ref="fileinput">
</div>
<div class="vicp-error" v-show="hasError">
<i class="vicp-icon2"></i> {{ errorMsg }}
</div>
<div class="vicp-operate">
<a @click="off" @mousedown="ripple">{{ lang.btn.off }}</a>
</div>
</div>
<div class="vicp-step2" v-if="step == 2">
<div class="vicp-crop">
<div class="vicp-crop-left" v-show="true">
<div class="vicp-img-container" @wheel.prevent="handleMouseWheel">
<img :src="sourceImgUrl" :style="sourceImgStyle" class="vicp-img" draggable="false"
@drag="preventDefault"
@dragstart="preventDefault"
@dragend="preventDefault"
@dragleave="preventDefault"
@dragover="preventDefault"
@dragenter="preventDefault"
@drop="preventDefault"
@touchstart="imgStartMove"
@touchmove="imgMove"
@touchend="createImg"
@touchcancel="createImg"
@mousedown="imgStartMove"
@mousemove="imgMove"
@mouseup="createImg"
@mouseout="createImg"
ref="img">
<div class="vicp-img-shade vicp-img-shade-1" :style="sourceImgShadeStyle"></div>
<div class="vicp-img-shade vicp-img-shade-2" :style="sourceImgShadeStyle"></div>
</div>
<div class="vicp-range">
<input type="range" v-model="scale.range" step="1" min="0" max="100" @mousemove="zoomChange">
<i @mousedown="startZoomSub" @mouseout="endZoomSub" @mouseup="endZoomSub" class="vicp-icon5"></i>
<i @mousedown="startZoomAdd" @mouseout="endZoomAdd" @mouseup="endZoomAdd" class="vicp-icon6"></i>
</div>
<div class="vicp-rotate" v-if="!noRotate">
<i @click="rotateImg">↻</i>
</div>
</div>
<div class="vicp-crop-right" v-show="true">
<div class="vicp-preview">
<div class="vicp-preview-item" v-if="!noSquare">
<img :src="createImgUrl" :style="previewStyle">
<span>{{ lang.preview }}</span>
</div>
<div class="vicp-preview-item vicp-preview-item-circle" v-if="!noCircle">
<img :src="createImgUrl" :style="previewStyle">
<span>{{ lang.preview }}</span>
</div>
</div>
</div>
</div>
<div class="vicp-operate">
<a @click="setStep(1)" @mousedown="ripple">{{ lang.btn.back }}</a>
<a class="vicp-operate-btn" @click="prepareUpload" @mousedown="ripple">{{ lang.btn.save }}</a>
</div>
</div>
<div class="vicp-step3" v-if="step == 3">
<div class="vicp-upload">
<span class="vicp-loading" v-show="loading === 1">{{ lang.loading }}</span>
<div class="vicp-progress-wrap">
<span class="vicp-progress" v-show="loading === 1" :style="progressStyle"></span>
</div>
<div class="vicp-error" v-show="hasError">
<i class="vicp-icon2"></i> {{ errorMsg }}
</div>
<div class="vicp-success" v-show="loading === 2">
<i class="vicp-icon3"></i> {{ lang.success }}
</div>
</div>
<div class="vicp-operate">
<a @click="setStep(2)" @mousedown="ripple">{{ lang.btn.back }}</a>
<a @click="off" @mousedown="ripple">{{ lang.btn.close }}</a>
</div>
</div>
<canvas v-show="false" :width="width" :height="height" ref="canvas"></canvas>
</div>
</div>
</template>
<script>
'use strict';
import language from './utils/language.js';
import mimes from './utils/mimes.js';
import data2blob from './utils/data2blob.js';
import effectRipple from './utils/effectRipple.js';
export default {
props: {
// 域,上传文件name,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
field: {
type: String,
'default': 'avatar'
},
show:{
type: Boolean,
'default': false
},
// 原名key,类似于id,触发事件会带上(如果一个页面多个图片上传控件,可以做区分
ki: {
type: String,
'default': '0'
},
// 显示该控件与否
modelValue: {
type: Boolean,
'default': true
},
// 上传地址
url: {
type: String,
'default': ''
},
// 其他要上传文件附带的数据,对象格式
params: {
type: Object,
'default': ()=>null
},
//Add custom headers
headers: {
type: Object,
'default': ()=>null
},
// 剪裁图片的宽
width: {
type: Number,
default: 200
},
// 剪裁图片的高
height: {
type: Number,
default: 200
},
// 不显示旋转功能
noRotate: {
type: Boolean,
default: true
},
// 不预览圆形图片
noCircle: {
type: Boolean,
default: false
},
// 不预览方形图片
noSquare: {
type: Boolean,
default: false
},
// 单文件大小限制
maxSize: {
type: Number,
'default': 10240
},
// 语言类型
langType: {
type: String,
'default': 'zh'
},
// 语言包
langExt: {
type: Object,
'default': ()=>null
},
// 图片上传格式
imgFormat: {
type: String,
'default': 'png'
},
// 图片背景 jpg情况下生效
imgBgc: {
type: String,
'default': '#fff'
},
// 是否支持跨域
withCredentials: {
type: Boolean,
'default': false
},
method: {
type: String,
'default': 'POST'
},
initialImgUrl: {
type: String,
'default': ''
},
allowImgFormat: {
type: Array,
'default': ()=>[
'gif',
'jpg',
'png'
]
}
},
data() {
let that = this,
{
imgFormat,
allowImgFormat,
langType,
langExt,
width,
height
} = that,
isSupported = true,
tempImgFormat = allowImgFormat.indexOf(imgFormat) === -1 ? 'jpg' : imgFormat,
lang = language[langType] ? language[langType] : language['en'],
mime = mimes[tempImgFormat];
// 规范图片格式
// that.imgFormat = tempImgFormat;
if (langExt) {
Object.assign(lang, langExt);
}
if (typeof FormData != 'function') {
isSupported = false;
}
return {
// 图片的mime
mime,
// 语言包
lang,
// 浏览器是否支持该控件
isSupported,
// 浏览器是否支持触屏事件
isSupportTouch: document.hasOwnProperty("ontouchstart"),
// 步骤
step: 1, //1选择文件 2剪裁 3上传
// 上传状态及进度
loading: 0, //0未开始 1正在 2成功 3错误
progress: 0,
// 是否有错误及错误信息
hasError: false,
errorMsg: '',
// 需求图宽高比
ratio: width / height,
// 原图地址、生成图片地址
sourceImg: null,
sourceImgUrl: this.initialImgUrl,
createImgUrl: this.initialImgUrl,
// 原图片拖动事件初始值
sourceImgMouseDown: {
on: false,
mX: 0, //鼠标按下的坐标
mY: 0,
x: 0, //scale原图坐标
y: 0
},
// 生成图片预览的容器大小
previewContainer: {
width: 100,
height: 100
},
// 原图容器宽高
sourceImgContainer: { // sic
width: 240,
height: 184 // 如果生成图比例与此一致会出现bug,先改成特殊的格式吧,哈哈哈
},
// 原图展示属性
scale: {
zoomAddOn: false, //按钮缩放事件开启
zoomSubOn: false, //按钮缩放事件开启
range: 1, //最大100
x: 0,
y: 0,
width: 0,
height: 0,
maxWidth: 0,
maxHeight: 0,
minWidth: 0, //最宽
minHeight: 0,
naturalWidth: 0, //原宽
naturalHeight: 0
}
}
},
computed: {
// 进度条样式
progressStyle() {
let {
progress
} = this;
return {
width: progress + '%'
}
},
// 原图样式
sourceImgStyle() {
let {
scale,
sourceImgMasking
} = this,
top = scale.y + sourceImgMasking.y + 'px',
left = scale.x + sourceImgMasking.x + 'px';
return {
top,
left,
width: scale.width + 'px',
height: scale.height + 'px',// 兼容 Opera
}
},
// 原图蒙版属性
sourceImgMasking() {
let {
width,
height,
ratio,
sourceImgContainer
} = this,
sic = sourceImgContainer,
sicRatio = sic.width / sic.height, // 原图容器宽高比
x = 0,
y = 0,
w = sic.width,
h = sic.height,
scale = 1;
if (ratio < sicRatio) {
scale = sic.height / height;
w = sic.height * ratio;
x = (sic.width - w) / 2;
}
if (ratio > sicRatio) {
scale = sic.width / width;
h = sic.width / ratio;
y = (sic.height - h) / 2;
}
return {
scale, // 蒙版相对需求宽高的缩放
x,
y,
width: w,
height: h
};
},
// 原图遮罩样式
sourceImgShadeStyle() {
let {
sourceImgMasking,
sourceImgContainer
} = this,
sic = sourceImgContainer,
sim = sourceImgMasking,
w = sim.width == sic.width ? sim.width : (sic.width - sim.width) / 2,
h = sim.height == sic.height ? sim.height : (sic.height - sim.height) / 2;
return {
width: w + 'px',
height: h + 'px'
};
},
previewStyle() {
let {
width,
height,
ratio,
previewContainer
} = this,
pc = previewContainer,
w = pc.width,
h = pc.height,
pcRatio = w / h;
if (ratio < pcRatio) {
w = pc.height * ratio;
}
if (ratio > pcRatio) {
h = pc.width / ratio;
}
return {
width: w*2 + 'px',
height: h*2 + 'px'
};
}
},
watch: {
modelValue(newValue) {
if (newValue && this.loading != 1) {
this.reset();
}
}
},
created(){
// 绑定按键esc隐藏此插件事件
document.addEventListener('keyup', this.handleEscClose )
},
beforeUnmount(){
document.removeEventListener('keyup', this.handleEscClose )
},
mounted() {
if (this.sourceImgUrl) {
this.startCrop();
}
},
methods: {
handleEscClose(e){
if(this.modelValue && (e.key == 'Escape' || e.keyCode == 27)){
this.off();
}
},
// 点击波纹效果
ripple(e) {
effectRipple(e);
},
// 关闭控件
off() {
this.step = 1
setTimeout(()=> {
this.$emit('closepop')
if(this.step == 3 && this.loading == 2){
this.setStep(1);
}
}, 200);
},
// 设置步骤
setStep(no) {
// 延时是为了显示动画效果呢,哈哈哈
setTimeout(()=> {
this.step = no;
}, 200);
},
/* 图片选择区域函数绑定
---------------------------------------------------------------*/
preventDefault(e) {
e.preventDefault();
return false;
},
handleClick(e) {
if (this.loading !== 1) {
if (e.target !== this.$refs.fileinput) {
e.preventDefault();
if (document.activeElement !== this.$refs) {
this.$refs.fileinput.click();
}
}
}
},
handleChange(e) {
e.preventDefault();
if (this.loading !== 1) {
let files = e.target.files || e.dataTransfer.files;
this.reset();
if (this.checkFile(files[0])) {
this.setSourceImg(files[0]);
}
}
},
/* ---------------------------------------------------------------*/
// 检测选择的文件是否合适
checkFile(file) {
let that = this,
{
lang,
maxSize
} = that;
// 仅限图片
if (file.type.indexOf('image') === -1) {
that.hasError = true;
that.errorMsg = lang.error.onlyImg;
return false;
}
// 超出大小
if (file.size / 1024 > maxSize) {
that.hasError = true;
that.errorMsg = lang.error.outOfSize + maxSize + 'kb';
return false;
}
return true;
},
// 重置控件
reset() {
let that = this;
that.loading = 0;
that.hasError = false;
that.errorMsg = '';
that.progress = 0;
},
// 设置图片源
setSourceImg(file) {
this.$emit('src-file-set', file.name, file.type, file.size);
let that = this,
fr = new FileReader();
fr.onload = function(e) {
that.sourceImgUrl = fr.result;
that.startCrop();
}
fr.readAsDataURL(file);
},
// 剪裁前准备工作
startCrop() {
let that = this,
{
width,
height,
ratio,
scale,
sourceImgUrl,
sourceImgMasking,
lang
} = that,
sim = sourceImgMasking,
img = new Image();
img.src = sourceImgUrl;
img.onload = function() {
let nWidth = img.naturalWidth,
nHeight = img.naturalHeight,
nRatio = nWidth / nHeight,
w = sim.width,
h = sim.height,
x = 0,
y = 0;
// 图片像素不达标
if (nWidth < width || nHeight < height) {
that.hasError = true;
that.errorMsg = lang.error.lowestPx + width + '*' + height;
return false;
}
if (ratio > nRatio) {
h = w / nRatio;
y = (sim.height - h) / 2;
}
if (ratio < nRatio) {
w = h * nRatio;
x = (sim.width - w) / 2;
}
scale.range = 0;
scale.x = x;
scale.y = y;
scale.width = w;
scale.height = h;
scale.minWidth = w;
scale.minHeight = h;
scale.maxWidth = nWidth * sim.scale;
scale.maxHeight = nHeight * sim.scale;
scale.naturalWidth = nWidth;
scale.naturalHeight = nHeight;
that.sourceImg = img;
that.createImg();
that.setStep(2);
};
},
// 鼠标按下图片准备移动
imgStartMove(e) {
e.preventDefault();
// 支持触摸事件,则鼠标事件无效
if(this.isSupportTouch && !e.targetTouches){
return false;
}
let et = e.targetTouches ? e.targetTouches[0] : e,
{
sourceImgMouseDown,
scale
} = this,
simd = sourceImgMouseDown;
simd.mX = et.screenX;
simd.mY = et.screenY;
simd.x = scale.x;
simd.y = scale.y;
simd.on = true;
},
// 鼠标按下状态下移动,图片移动
imgMove(e) {
e.preventDefault();
// 支持触摸事件,则鼠标事件无效
if(this.isSupportTouch && !e.targetTouches){
return false;
}
let et = e.targetTouches ? e.targetTouches[0] : e,
{
sourceImgMouseDown: {
on,
mX,
mY,
x,
y
},
scale,
sourceImgMasking
} = this,
sim = sourceImgMasking,
nX = et.screenX,
nY = et.screenY,
dX = nX - mX,
dY = nY - mY,
rX = x + dX,
rY = y + dY;
if (!on) return;
if (rX > 0) {
rX = 0;
}
if (rY > 0) {
rY = 0;
}
if (rX < sim.width - scale.width) {
rX = sim.width - scale.width;
}
if (rY < sim.height - scale.height) {
rY = sim.height - scale.height;
}
scale.x = rX;
scale.y = rY;
},
// 顺时针旋转图片
rotateImg(e) {
let {
sourceImg,
scale: {
naturalWidth,
naturalHeight,
}
} = this,
width = naturalHeight,
height = naturalWidth,
canvas = this.$refs.canvas,
ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.fillRect(0, 0, width, height);
ctx.translate(width, 0);
ctx.rotate(Math.PI * 90 / 180);
ctx.drawImage(sourceImg, 0, 0, naturalWidth, naturalHeight);
let imgUrl = canvas.toDataURL(mimes['png']);
this.sourceImgUrl = imgUrl;
this.startCrop();
},
handleMouseWheel(e){
e = e || window.event;
let { scale } = this;
if (e.wheelDelta) { //判断浏览器IE,谷歌滑轮事件
if (e.wheelDelta > 0) { //当滑轮向上滚动时
this.zoomImg(scale.range >= 100 ? 100 : ++scale.range);
}
if (e.wheelDelta < 0) {
this.zoomImg(scale.range <= 0 ? 0 : --scale.range);
}
} else if (e.detail) { //Firefox滑轮事件
if (e.detail > 0) { //当滑轮向上滚动时
this.zoomImg(scale.range >= 100 ? 100 : ++scale.range);
}
if (e.detail < 0) {
this.zoomImg(scale.range <= 0 ? 0 : --scale.range);
}
}
},
// 按钮按下开始放大
startZoomAdd(e) {
let that = this,
{
scale
} = that;
scale.zoomAddOn = true;
function zoom() {
if (scale.zoomAddOn) {
let range = scale.range >= 100 ? 100 : ++scale.range;
that.zoomImg(range);
setTimeout(function() {
zoom();
}, 60);
}
}
zoom();
},
// 按钮松开或移开取消放大
endZoomAdd(e) {
this.scale.zoomAddOn = false;
},
// 按钮按下开始缩小
startZoomSub(e) {
let that = this,
{
scale
} = that;
scale.zoomSubOn = true;
function zoom() {
if (scale.zoomSubOn) {
let range = scale.range <= 0 ? 0 : --scale.range;
that.zoomImg(range);
setTimeout(function() {
zoom();
}, 60);
}
}
zoom();
},
// 按钮松开或移开取消缩小
endZoomSub(e) {
let {
scale
} = this;
scale.zoomSubOn = false;
},
zoomChange(e) {
this.zoomImg(e.target.value);
},
// 缩放原图
zoomImg(newRange) {
let that = this,
{
sourceImgMasking,
sourceImgMouseDown,
scale
} = this,
{
maxWidth,
maxHeight,
minWidth,
minHeight,
width,
height,
x,
y,
range
} = scale,
sim = sourceImgMasking,
// 蒙版宽高
sWidth = sim.width,
sHeight = sim.height,
// 新宽高
nWidth = minWidth + (maxWidth - minWidth) * newRange / 100,
nHeight = minHeight + (maxHeight - minHeight) * newRange / 100,
// 新坐标(根据蒙版中心点缩放)
nX = sWidth / 2 - (nWidth / width) * (sWidth / 2 - x),
nY = sHeight / 2 - (nHeight / height) * (sHeight / 2 - y);
// 判断新坐标是否超过蒙版限制
if (nX > 0) {
nX = 0;
}
if (nY > 0) {
nY = 0;
}
if (nX < sWidth - nWidth) {
nX = sWidth - nWidth;
}
if (nY < sHeight - nHeight) {
nY = sHeight - nHeight;
}
// 赋值处理
scale.x = nX;
scale.y = nY;
scale.width = nWidth;
scale.height = nHeight;
scale.range = newRange;
setTimeout(function() {
if (scale.range == newRange) {
that.createImg();
}
}, 300);
},
// 生成需求图片
createImg(e) {
let that = this,
{
imgFormat,
imgBgc,
mime,
sourceImg,
scale: {
x,
y,
width,
height,
},
sourceImgMasking: {
scale
}
} = that,
canvas = that.$refs.canvas,
ctx = canvas.getContext('2d');
if (e) {
// 取消鼠标按下移动状态
that.sourceImgMouseDown.on = false;
}
canvas.width = that.width;
canvas.height = that.height;
ctx.clearRect(0, 0, that.width, that.height);
if(imgFormat == 'png'){
ctx.fillStyle = 'rgba(0,0,0,0)';
} else{
// 如果jpg 为透明区域设置背景,默认白色
ctx.fillStyle = imgBgc;
}
ctx.fillRect(0, 0, that.width, that.height);
ctx.drawImage(sourceImg, x / scale, y / scale, width / scale, height / scale);
that.createImgUrl = canvas.toDataURL(mime);
},
prepareUpload(){
let {
url,
createImgUrl,
field,
ki
} = this;
this.$emit('crop-success', createImgUrl, field, ki);
if(typeof url == 'string' && url){
this.upload();
}else{
this.off();
}
},
// 上传图片
upload() {
let that = this,
{
lang,
imgFormat,
mime,
url,
params,
headers,
field,
ki,
createImgUrl,
withCredentials,
method
} = this,
fmData = new FormData();
// 添加其他参数
if (typeof params == 'object' && params) {
Object.keys(params).forEach((k) => {
fmData.append(k, params[k]);
})
}
// 将field的添加放到表单域的最后,以支持阿里云OSS的表单上传
fmData.append(field, data2blob(createImgUrl, mime), field + '.' + imgFormat);
// 监听进度回调
const uploadProgress = function(event) {
if (event.lengthComputable) {
that.progress = 100 * Math.round(event.loaded) / event.total;
}
};
// 上传文件
that.reset();
that.loading = 1;
that.setStep(3);
new Promise(function(resolve, reject) {
let client = new XMLHttpRequest();
client.open(method, url, true);
client.withCredentials = withCredentials;
client.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200 || this.status === 201 || this.staus ===202 ) {
resolve(JSON.parse(this.responseText));
} else {
reject(this.status);
}
};
client.upload.addEventListener("progress", uploadProgress, false); //监听进度
// 设置header
if (typeof headers == 'object' && headers) {
Object.keys(headers).forEach((k) => {
client.setRequestHeader(k, headers[k]);
})
}
client.send(fmData);
}).then(
// 上传成功
function(resData) {
if (that.modelValue) {
that.loading = 2;
that.$emit('crop-upload-success', resData, field, ki);
}
},
// 上传失败
function(sts) {
if (that.modelValue) {
that.loading = 3;
that.hasError = true;
that.errorMsg = lang.fail;
that.$emit('crop-upload-fail', sts, field, ki);
}
}
);
}
},
}
</script>
<!--
<style lang='sass' src="./scss/upload.scss">
</style> -->
<style>
@import url('upload.css');
</style>
第二步,在你引用此插件的vue文件里添加以下两个重要方法
<my-upload
ref="myUpload"
@closepop="closepop()"//重要
:show="colshow"//重要
:field="uuid()+'.jpg'"
@crop-success="cropSuccess"
:srcFileSet="srcFileSet"
@cropUploadFail="cropUploadFail"
@cropUploadSuccess="cropUploadSuccess"
:width="800"
:height="450"
img-format="jpg"
:size="size"
:noCircle="true"
:langExt="lang"
:params="params"
></my-upload>
第三步在script里定义
export default {
data() {
return {
colshow: false,
}
},
methods:{
closepop(){
this.colshow = !this.colshow
},
}
}
成功解决,有不懂的可以留言