在package.json中添加
"vue-cropperjs": "4",
后在控制台执行:npm install
ImageCropper.vue
<template>
<div v-if="src">
<!-- Vue Cropper区域 -->
<el-row class="cropper-wrapper" v-if="src">
<el-col :span="24">
<vue-cropper
v-if="src"
:key="cropperKey"
ref="cropper"
:src="src"
:style="{ width: width + 'px', height: height + 'px' }"
@ready="onReady"
:dragMode="'move'"
:viewMode="1"
:cropBoxResizable="false"
/>
</el-col>
</el-row>
<el-row class="button-row" v-if="src">
<el-col :span="24">
<el-button class="button-soft-orange" @click="cropImage" icon="el-icon-scissors">裁剪图像</el-button>
<el-button class="button-soft-blue" @click="downloadImage" icon="el-icon-download">下载图像</el-button>
<el-button class="button-soft-red" @click.prevent="zoom(0.2)" icon="el-icon-zoom-in" />
<el-button class="button-soft-red" @click.prevent="zoom(-0.2)" icon="el-icon-zoom-out" />
<el-button class="button-soft-yellow" @click.prevent="move(-10, 0)" icon="el-icon-arrow-left" />
<el-button class="button-soft-yellow" @click.prevent="move(10, 0)" icon="el-icon-arrow-right" />
<el-button class="button-soft-yellow" @click.prevent="move(0, -10)" icon="el-icon-arrow-up" />
<el-button class="button-soft-yellow" @click.prevent="move(0, 10)" icon="el-icon-arrow-down" />
<el-button class="button-soft-blue" ref="flipX" @click.prevent="flipX" icon="el-icon-sort" />
<el-button class="button-soft-blue" ref="flipY" @click.prevent="flipY" icon="el-icon-sort" />
<el-button class="button-soft-green" @click.prevent="rotate(90)" icon="el-icon-refresh-right" />
<el-button class="button-soft-green" @click.prevent="rotate(-90)" icon="el-icon-refresh-left" />
<el-button class="button-soft-gray" @click.prevent="reset" icon="el-icon-refresh" />
</el-col>
</el-row>
<el-row v-if="croppedImageUrl">
<el-col :style="{ width: width + 'px' }" >
<el-card>
<h3>裁剪后预览:</h3>
<div class="preview-and-buttons">
<div class="image-preview-wrapper">
<img class="cropped-image-preview" :src="croppedImageUrl" alt="Cropped Image" />
</div>
<div class="upload-button-col">
<el-button class="button-soft-green" @click="uploadImageToCos" icon="el-icon-upload">
上传到腾讯云
</el-button>
<el-button class="button-soft-gray" @click="resetImage">关闭裁剪区和预览区</el-button>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import VueCropper from "vue-cropperjs"
import "cropperjs/dist/cropper.css"
import { Button, Row, Col, Card } from "element-ui"
import COS from 'cos-js-sdk-v5'
export default {
components: {
VueCropper,
'el-button': Button,
'el-row': Row,
'el-col': Col,
'el-card': Card
},
props: {
width: {
type: Number,
default: 600
},
height: {
type: Number,
default: 600
},
centerWidth: {
type: Number
}
},
data() {
return {
src: '', // 初始 src 为空,等待用户选择本地图片
cropperKey: 0, // 用于强制重新渲染 cropper
croppedImageUrl: '',
uploadedImageUrl: '', // 用于存储上传后返回的 URL
flipXValue: 1,
flipYValue: 1,
cos: null // 用于存储腾讯云COS实例
}
},
mounted() {
this.cos = new COS({
SecretId: '', // 写自己的腾讯云 SecretId
SecretKey: '' // 写自己的腾讯云 SecretKey
});
},
methods: {
onReady() {
// 重置画布和裁剪框,确保初始图片位置和大小如重置后的效果
this.reset();
},
onImageChange(event) {
const file = event.target.files[0];
if (file) {
this.src = URL.createObjectURL(file);
this.cropperKey++; // 更新 key 强制重新渲染 vue-cropper
this.$emit('imageChanged'); // 通知父组件图片已更换
}
},
cropImage() {
this.$refs.cropper.cropper.getCroppedCanvas().toBlob((blob) => {
this.croppedImageUrl = URL.createObjectURL(blob);
});
},
downloadImage() {
this.$refs.cropper.cropper.getCroppedCanvas().toBlob((blob) => {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'cropped_image.png';
link.click();
});
},
zoom(scale) {
this.$refs.cropper.cropper.zoom(scale);
},
move(offsetX, offsetY) {
this.$refs.cropper.cropper.move(offsetX, offsetY);
},
flipX() {
this.flipXValue = this.flipXValue === 1 ? -1 : 1;
this.$refs.cropper.cropper.scaleX(this.flipXValue);
},
flipY() {
this.flipYValue = this.flipYValue === 1 ? -1 : 1;
this.$refs.cropper.cropper.scaleY(this.flipYValue);
},
rotate(degrees) {
this.$refs.cropper.cropper.rotate(degrees);
},
reset() {
// 先重置裁剪框和画布确保其他变换恢复到初始状态
this.$refs.cropper.cropper.reset();
// 获取图片原始尺寸
const imageData = this.$refs.cropper.cropper.getImageData();
// 计算新的高度
const newHeight = (this.centerWidth / imageData.naturalWidth) * imageData.naturalHeight;
// 设置画布大小并居中
this.$refs.cropper.cropper.setCanvasData({
width: this.centerWidth,
height: newHeight,
left: (this.width - this.centerWidth) / 2, // 居中画布
top: (this.height - newHeight) / 2 // 居中画布
});
// 设置裁剪框的大小和居中位置
this.$refs.cropper.cropper.setCropBoxData({
left: (this.width - 300) / 2, // 居中裁剪框
top: (this.height - 200) / 2, // 居中裁剪框
width: 300,
height: 200
});
this.flipXValue = 1;
this.flipYValue = 1;
},
resetImage() {
this.src = ''; // 重置 src,使用户可以重新选择图片
this.cropperKey++; // 更新 key 强制重新渲染 vue-cropper
this.$emit('imageChanged'); // 通知父组件图片已重置
this.croppedImageUrl = ''
},
uploadImageToCos() {
this.uploadedImageUrl = '';
this.$emit('uploadStarted'); // 通知父组件上传开始,清空输入框
this.$refs.cropper.cropper.getCroppedCanvas().toBlob((blob) => {
const timestamp = new Date().getTime(); // 使用时间戳生成唯一文件名
const fileName = `cropped_image_${timestamp}.png`; // 生成唯一文件名
const file = new File([blob], fileName, { type: "image/png" });
this.cos.uploadFile({
Bucket: '', // 写自己的腾讯云 Bucket
Region: '', // 写自己的腾讯云 Region
Key: fileName, // 使用唯一文件名
Body: file,
SliceSize: 1024 * 1024, // 分块大小,单位为字节,这里设置为1MB
}, (err, data) => {
if (err) {
this.$notify.error({
title: '上传失败',
message: '图片上传到腾讯云失败,请重试。',
duration: 1000
});
this.$emit('uploadFailed', err); // 通知父组件上传失败
} else {
this.uploadedImageUrl = data.Location;
this.$notify.success({
title: '上传成功',
message: '图片成功上传到腾讯云。',
duration: 1000
});
this.$emit('uploaded', this.uploadedImageUrl); // 通知父组件上传成功,并发送 URL
}
});
});
},
openFileInput() {
this.$refs.fileInput.click(); // 模拟点击文件输入控件
}
}
}
</script>
<style scoped>
.image-select-wrapper {
margin-bottom: 20px;
}
.button-soft-green {
background-color: #a5d6a7 !important;
border-color: #a5d6a7 !important;
color: #fff !important;
}
.button-soft-orange {
background-color: #ffcc80 !important;
border-color: #ffcc80 !important;
color: #fff !important;
}
.button-soft-yellow {
background-color: #f3c672b4 !important;
border-color: #f3c672b4 !important;
color: #fff !important;
}
.button-soft-blue {
background-color: #bbd0f8 !important;
border-color: #bbd0f8 !important;
color: #fff !important;
}
.button-soft-gray {
background-color: #cfd8dc !important;
border-color: #cfd8dc !important;
color: #fff !important;
}
.button-soft-red {
background-color: #ff8a80 !important;
border-color: #ff8a80 !important;
color: #fff !important;
}
.cropper-wrapper {
margin-bottom: 20px;
}
.button-row {
margin-bottom: 20px;
}
.cropped-image-preview {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-button-col {
display: flex;
gap: 10px; /* 水平按钮间距 */
align-items: center;
}
.preview-and-buttons {
display: flex;
align-items: center;
gap: 20px; /* 图片与按钮组之间的间距 */
}
.image-preview-wrapper {
width: 300px!important;
/* height: 200px!important; */
border: 1px solid #dcdcdc;
display: flex;
align-items: center;
justify-content: center;
}
.image-preview-wrapper img {
max-width: 100%;
/* max-height: 100%; */
object-fit: cover;
}
</style>
TestView.vue
<template>
<div :style="{ width: parentWidth + 'px' }">
<div class="input-wrapper" :style="{ width: inputWrapperWidth + 'px' }">
<el-button @click="openFileInput" style="margin-right: 10px;">选择图片</el-button>
<el-input :value="uploadedImageUrl" placeholder="上传后的URL" readonly/>
<input type="file" ref="fileInput" @change="onFileChange" accept="image/*" style="display: none" />
</div>
<ImageCropper
ref="imageCropper"
@uploaded="handleUploaded"
@uploadFailed="handleUploadFailed"
@imageChanged="clearUploadedImageUrl"
@uploadStarted="clearUploadedImageUrl"
@getLocalImageUrl="handleGetLocalImageUrl"
:width="cropperWidth"
:height="cropperHeight"
:center-width="setCenterWidth"
style="margin-top: 10px;"
/>
</div>
</template>
<script>
import ImageCropper from '@/components7/ImageCropper.vue'
export default {
components: {
ImageCropper
},
data() {
return {
uploadedImageUrl: '', // 用于存储上传成功后的 URL
parentWidth: 1000, // 父组件宽度
inputWrapperWidth: 1000, // input-wrapper宽度
cropperWidth: 1000, // ImageCropper宽度
cropperHeight: 1000, // ImageCropper高度
setCenterWidth:900,
localImageUrl: '' // 本地图片
}
},
methods: {
handleUploaded(url) {
this.uploadedImageUrl = url;
},
handleUploadFailed(err) {
console.error('上传失败:', err);
},
clearUploadedImageUrl() {
this.uploadedImageUrl = '';
},
openFileInput() {
this.$refs.fileInput.click();
},
onFileChange(event) {
const file = event.target.files[0];
if (file) {
this.$refs.imageCropper.onImageChange(event);
}
},
handleGetLocalImageUrl(url) {
this.localImageUrl = url;
console.log("this.localImageUrl:",this.localImageUrl)
}
}
}
</script>
<style scoped>
.input-wrapper {
display: flex;
align-items: center;
}
.input-wrapper .el-input {
flex: 1;
}
</style>