1. 引入 vue-cropper 第三方插件
npm install vue-cropper
yarn add vue-cropper
2. 在 main.js 全局进入
import VueCropper from 'vue-cropper'
Vue.use(VueCropper)
3. 封装成组件
3.1 上传组件
<template>
<div class="ant-upload-preview">
<div class="avatatImg">
<a-upload
name="avatar"
listType="picture-card"
:showUploadList="false"
:beforeUpload="beforeUpload"
:customRequest="function () {}"
@change="handleChange"
accept="image/png,image/jpeg"
>
<img class="upload_img" v-if="imageUrl" :src="imageUrl" alt="avatar" />
<div v-else>
<a-icon :type="loading ? 'loading' : 'plus'" />
<div class="ant-upload-text">上传图片</div>
</div>
</a-upload>
<div class="text">
<div>{{ avatarTextTop }}</div>
<div>{{ avatarTextBottom }}</div>
</div>
</div>
<!-- 引入裁剪组件 -->
<CropperModal
ref="CropperModal"
:imgType="imgType"
:fileName="fileName"
:cropperMode="cropperMode"
@cropper-no="handleCropperClose"
@cropper-ok="handleCropperSuccess"
></CropperModal>
</div>
</template>
<script>
import { message } from "ant-design-vue";
import CropperModal from "./CropperModal";
export default {
components: { CropperModal },
props: {
//图片裁切配置
options: {
type: Object,
default: function () {
return {
autoCrop: true, //是否默认生成截图框
autoCropWidth: 1029, //默认生成截图框宽度
autoCropHeight: 480, //默认生成截图框高度
fixedBox: true, //是否固定截图框大小 不允许改变
previewsCircle: false, //预览图是否是原圆形
title: "修改图片",
};
},
},
avatarTextTop: {
type: String,
default: "推荐使用160*160px,JPG.PNG.JPEG格式",
},
avatarTextBottom: {
type: String,
default: "图片小于1M",
},
// 上传图片的大小,单位M
imgSize: {
type: Number,
default: 2,
},
//图片存储在oss上的上级目录名
imgType: {
type: String,
default: "",
},
// 图片地址
imageUrl: {
type: String,
default: "",
},
},
data() {
return {
fileName: "", // 文件名
loading: false,
isStopRun: false,
cropperMode: 'contain' // 图片渲染方式
};
},
methods: {
//从本地选择文件
handleChange(info) {
if (!info.file.status) {
this.loading = false;
} else {
this.loading = true;
}
this.fileName = info.file.name;
if (this.isStopRun) {
return;
}
const { options } = this;
this.getBase64(info.file.originFileObj, (imageUrl) => {
const target = Object.assign({}, options, {
img: imageUrl,
});
this.$refs.CropperModal.edit(target);
});
},
// 上传之前 格式与大小校验
beforeUpload(file) {
this.$emit("avatarLoadingFn", true);
const isJpgOrPng =
file.type === "image/jpeg" || file.type === "image/png";
if (!isJpgOrPng) {
message.error("不能上传其他类型的图片");
}
// 获取上传图片尺寸
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
var img = new Image()
img.src = reader.result
img.onload = () => {
// 1、先根据图片裁剪的尺寸
if (this.options.autoCropWidth > this.options.autoCropHeight) {
// 2、再根据上传图片尺寸
this.cropperMode = this.options.autoCropWidth + 'px auto'
} else {
if (img.width >= img.height) {
this.cropperMode = 'auto ' + this.options.autoCropHeight + 'px'
} else {
this.cropperMode = this.options.autoCropWidth + 'px auto'
}
}
}
}
return isJpgOrPng;
},
//获取服务器返回的地址
handleCropperSuccess(data) {
//将返回的数据回显
this.loading = false;
this.$emit("avatarfn", data);
},
// 取消上传
handleCropperClose() {
this.loading = false;
},
getBase64(img, callback) {
if (img) {
const reader = new FileReader();
reader.addEventListener("load", () => callback(reader.result));
reader.readAsDataURL(img);
}
},
},
};
</script>
<style lang="less" scoped>
.avatar-upload-wrapper {
height: 180px;
width: 100%;
}
.ant-upload-preview {
background-color: #fff;
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.upload_img {
width: 100%;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
}
.ant-upload-picture-card-wrapper {
width: auto;
}
.avatar-uploader > .ant-upload {
width: 128px;
height: 128px;
}
.avatatImg {
// width: 100%;
display: flex;
align-items: center;
.text {
margin-left: 20px;
div {
line-height: 30px !important;
font-weight: 600;
}
}
}
</style>
3.2 裁剪组件
<template>
<a-modal
:visible="visible"
:title="options.title"
:maskClosable="false"
:confirmLoading="confirmLoading"
:width="1200"
@cancel="cancelHandel"
>
<a-row>
<a-col :xs="24" :md="24" :style="{ height: '600px' }">
<vue-cropper
ref="cropper"
:img="options.img"
:info="true"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixedBox="options.fixedBox"
:mode="cropperMode"
@realTime="realTime"
>
</vue-cropper>
</a-col>
<!-- <a-col :xs="24" :md="12" :style="{ height: '250px' }">
<div
:class="
options.previewsCircle
? 'avatar-upload-preview'
: 'avatar-upload-preview_range'
"
>
<img :src="previews.url" :style="previews.img" />
</div>
</a-col> -->
</a-row>
<template slot="footer">
<a-button key="back" @click="cancelHandel">取消</a-button>
<a-button
key="submit"
type="primary"
:loading="confirmLoading"
@click="okHandel"
>保存</a-button
>
</template>
</a-modal>
</template>
<script>
import axios from "axios";
import { domainName } from "@/config/index"; // 后台服务器的域名
export default {
props: {
//图片存储在oss上的上级目录名
imgType: {
type: String,
default: "",
},
fileName: {
type: String, // 文件名
default: "",
},
cropperMode: { // 图片渲染方式
type: String,
default: 'contain'
}
},
data() {
return {
visible: false,
img: null,
confirmLoading: false,
options: {
img: "", //裁剪图片的地址
autoCrop: true, //是否默认生成截图框
autoCropWidth: 180, //默认生成截图框宽度
autoCropHeight: 180, //默认生成截图框高度
fixedBox: true, //是否固定截图框大小 不允许改变
previewsCircle: false, //预览图是否是原圆形
title: "修改图片",
},
previews: {},
url: {
upload: "/sys/common/saveToImgByStr",
},
};
},
methods: {
edit(record) {
const { options } = this;
this.visible = true;
this.options = Object.assign({}, options, record);
},
// 取消截图
cancelHandel() {
this.confirmLoading = false;
this.visible = false;
this.$emit("cropper-no");
},
// 确认截图
okHandel() {
const that = this;
that.confirmLoading = true;
// 获取截图的base64 数据
this.$refs.cropper.getCropData((data) => {
let file = this.dataURLtoFile(data, this.fileName);
let formData = new window.FormData();
formData.append("uploadFile", file);
axios
.post(`${domainName}/admin/files/upload/images`, formData)
.then((res) => {
if (res.data.code == 200) {
this.visible = false;
this.confirmLoading = false;
this.$emit("cropper-ok", res.data.data);
}
});
});
},
//移动框的事件
realTime(data) {
this.previews = data;
},
// base 64 转成二进制文件流
dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
},
},
};
</script>
<style lang="less" scoped>
.avatar-upload-preview_range,
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 100%;
height: 100%;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
img {
background-color: red;
height: 100%;
}
}
.avatar-upload-preview_range {
border-radius: 0;
}
</style>
4. 在页面中使用
4.1 HTML
<CropperUpload
:imageUrl="form.coverUrl"
:options="coverOptions"
@avatarfn="coverUrlFn"
avatarTextTop="推荐使用174px*200px JPG.PNG.JPEG格式 图片小于1M"
avatarTextBottom=""
/>
// imageurl --> 图片地址
// options --> 截图配置项
// coverUrlFn -- > 接收需要传递回后台的参数
4.2 options
export default {
data(){
return {
coverOptions: {
autoCrop: true, //是否默认生成截图框
autoCropWidth: 174, //默认生成截图框宽度
autoCropHeight: 200, //默认生成截图框高度
fixedBox: true, //是否固定截图框大小 不允许改变
previewsCircle: false, //预览图是否是原圆形
title: "修改图片",
},
}
}
}
5. 效果图