前言
elementui本身是不支持做图片裁剪功能的, 要实现这个功能可以使用canvans,但是要对canvans很熟悉, 也可以引入第三方插件来实现,这样简单一点, 这里就是走第三方插件的方式
一、引包
npm install vue-cropper --save
二、vue中引入
import { VueCropper } from "vue-cropper";
在export default
下声明组件
components: { VueCropper },
三、改造elementui自带的上传组件
el-upload
相关代码
<el-upload multiple action="#" list-type="picture-card" :on-change="fileAdd" :before-upload="handleBeforeUpload"
:limit="limit" :on-exceed="handleExceed" ref="imageUpload" :on-remove="handleDelete" :show-file-list="true"
:headers="headers" :file-list="fileList" :on-preview="handlePictureCardPreview"
:class="{ hide: this.fileList.length >= this.limit }" :auto-upload="false">
<i class="el-icon-plus"></i>
</el-upload>
1. 使用vue-cropper组件
<el-dialog title="图片裁剪" :visible.sync="cropperOpen" width="700px" append-to-body>
<div class="cropper">
<vue-cropper ref="cropper" :img="option.img" :output-size="option.size" :output-type="option.outputType"
:info="true" :full="option.full" :can-move="option.canMove" :can-move-box="option.canMoveBox"
:original="option.original" :auto-crop="option.autoCrop" :fixed="option.fixed"
:fixed-number="option.fixedNumber" :center-box="option.centerBox" :info-true="option.infoTrue"
:fixed-box="option.fixedBox" :auto-crop-width="option.autoCropWidth"
:auto-crop-height="option.autoCropHeight" />
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
PS: vue-cropper组件一定要使用外层容器包裹并设置宽高, 不然显示不出来
相关变量声明:
return {
cropperOpen: false,
// 裁剪组件的基础配置option
option: {
img: "", // 裁剪图片的地址
info: true, // 裁剪框的大小信息
outputSize: 1, // 裁剪生成图片的质量
outputType: "png", // 裁剪生成图片的格式
canScale: true, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
canMoveBox: true, // 截图框能否拖动
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: false, // 固定截图框大小 不允许改变
fixed: false, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
full: false, // 是否输出原图比例的截图
original: false, // 上传图片按照原始比例渲染
centerBox: true, // 截图框是否被限制在图片里面
infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
},
2. 修改el-upload的on-change事件
fileAdd(file, fileList) {
const v = this.handleBeforeUpload(file.raw);
// 上传成功后将图片地址赋值给裁剪框显示图片
if (v || v == undefined) {
this.$nextTick(async () => {
const reader = new FileReader();
reader.onload = (e) => {
this.option.img = e.target.result;
};
reader.readAsDataURL(file.raw);
this.cropperOpen = true;
});
}else {
this.fileList = [];
}
},
其中this.handleBeforeUpload
是上传之前进行校验的, 比如图片大小、类型等, 主要是下面的reader, 先读取选择的图片, 然后拿到对应的base64编码赋值给vue-cropper
组件, 用于展示可裁剪图片
3. 编写弹窗确认裁剪的submitForm方法
submitForm() {
this.$refs.cropper.getCropBlob((blob) => {
this.cropperOpen = false;
let file = new File([blob], "Jecircle" + new Date().getTime() + ".png", {
type: "image/png",
lastModified: Date.now(),
});
this.uploadList.push({
name: file.name,
url: URL.createObjectURL(blob),
file: file,
});
this.uploadedSuccessfully();
});
},
通过vue-cropper
组件自带的方法获取到裁剪后的图片blob对象, 然后转成File, 设置相应的名称和类型, 存放到待上传的uploadList中去
四、效果
如果还要对裁剪添加一些功能, 可以在弹窗中增加相应的按钮, 然后调用vue-cropper
自带的一些api就可以了
五、源码
<template>
<div class="component-upload-image">
<el-upload multiple action="#" list-type="picture-card" :on-change="fileAdd" :before-upload="handleBeforeUpload"
:limit="limit" :on-exceed="handleExceed" ref="imageUpload" :on-remove="handleDelete" :show-file-list="true"
:headers="headers" :file-list="fileList" :on-preview="handlePictureCardPreview"
:class="{ hide: this.fileList.length >= this.limit }" :auto-upload="false">
<i class="el-icon-plus"></i>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" slot="tip" v-if="showTip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
的文件
</div>
<el-dialog :visible.sync="dialogVisible" title="预览" width="800" append-to-body>
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
</el-dialog>
<!-- 裁剪 -->
<el-dialog title="图片裁剪" :visible.sync="cropperOpen" width="700px" append-to-body>
<div class="cropper">
<vue-cropper ref="cropper" :img="option.img" :output-size="option.size" :output-type="option.outputType"
:info="true" :full="option.full" :can-move="option.canMove" :can-move-box="option.canMoveBox"
:original="option.original" :auto-crop="option.autoCrop" :fixed="option.fixed"
:fixed-number="option.fixedNumber" :center-box="option.centerBox" :info-true="option.infoTrue"
:fixed-box="option.fixedBox" :auto-crop-width="option.autoCropWidth"
:auto-crop-height="option.autoCropHeight" />
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getToken } from "@/utils/auth";
import { VueCropper } from "vue-cropper";
export default {
components: { VueCropper },
props: {
value: [String, Object, Array],
// 图片数量限制
limit: {
type: Number,
default: 1,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 5,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"],
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true,
},
},
data() {
return {
number: 0,
uploadList: [],
dialogImageUrl: "",
dialogVisible: false,
hideUpload: false,
baseUrl: process.env.VUE_APP_BASE_API,
headers: {
Authorization: "Bearer " + getToken(),
},
fileList: [],
deleteIds: "",
cropperOpen: false,
// 裁剪组件的基础配置option
option: {
img: "", // 裁剪图片的地址
info: true, // 裁剪框的大小信息
outputSize: 1, // 裁剪生成图片的质量
outputType: "png", // 裁剪生成图片的格式
canScale: true, // 图片是否允许滚轮缩放
autoCrop: true, // 是否默认生成截图框
canMoveBox: true, // 截图框能否拖动
autoCropWidth: 200, // 默认生成截图框宽度
autoCropHeight: 200, // 默认生成截图框高度
fixedBox: false, // 固定截图框大小 不允许改变
fixed: false, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
full: false, // 是否输出原图比例的截图
original: false, // 上传图片按照原始比例渲染
centerBox: true, // 截图框是否被限制在图片里面
infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
},
};
},
watch: {
value: {
handler(val) {
if (val) {
// 首先将值转为数组
const list = Array.isArray(val) ? val : this.value.split(",");
// 然后将数组转为对象数组
this.fileList = list.map((item) => {
if (
item.imageUrl.indexOf("http://") === -1 &&
item.imageUrl.indexOf("https://") === -1
) {
item = {
name: item.imageName,
url: this.baseUrl + item.imageUrl,
id: item.rowGuid,
};
} else {
item = { name: item.imageName, url: item.imageUrl, id: item.rowGuid };
}
return item;
});
} else {
this.fileList = [];
return [];
}
},
deep: true,
immediate: true,
},
},
computed: {
// 是否显示提示
showTip() {
return this.isShowTip && (this.fileType || this.fileSize);
},
},
methods: {
// 上传前loading加载
handleBeforeUpload(file) {
let isImg = false;
if (this.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
isImg = this.fileType.some((type) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
} else {
isImg = file.type.indexOf("image") > -1;
}
if (!isImg) {
this.$modal.msgError(
`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`
);
return false;
}
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
this.$modal.loading("请稍候...");
this.number++;
},
// 文件个数超出
handleExceed() {
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
},
// 上传文件到本地
fileAdd(file, fileList) {
const v = this.handleBeforeUpload(file.raw);
// 上传成功后将图片地址赋值给裁剪框显示图片
if (v || v == undefined) {
this.$nextTick(async () => {
const reader = new FileReader();
reader.onload = (e) => {
this.option.img = e.target.result;
};
reader.readAsDataURL(file.raw);
this.cropperOpen = true;
});
}else {
this.fileList = [];
}
},
// 删除图片
handleDelete(file) {
const findex = this.fileList.map((f) => f.name).indexOf(file.name);
if (findex > -1) {
this.fileList.splice(findex, 1);
let files = this.fileList.map((item) => item.file);
this.$emit("input", files);
if (file.id) {
this.deleteIds += file.id + ",";
this.$emit("delete", this.deleteIds);
}
}
},
// 上传结束处理
uploadedSuccessfully() {
if (this.number > 0 && this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
let files = this.fileList.map((item) => item.file);
this.$emit("input", files);
this.$modal.closeLoading();
}
},
// 预览
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
// 对象转成指定字符串分隔
listToString(list, separator) {
let strs = "";
separator = separator || ",";
for (let i in list) {
if (list[i].url) {
strs += list[i].url.replace(this.baseUrl, "") + separator;
}
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
},
//确定裁剪
submitForm() {
// 获取截图的 blob 数据
this.$refs.cropper.getCropBlob((blob) => {
this.cropperOpen = false;
let file = new File([blob], "Jecircle" + new Date().getTime() + ".png", {
type: "image/png",
lastModified: Date.now(),
});
this.uploadList.push({
name: file.name,
url: URL.createObjectURL(blob),
file: file,
});
this.uploadedSuccessfully();
});
},
cancel() {
this.cropperOpen = false;
},
},
};
</script>
<style scoped lang="scss">
// .el-upload--picture-card 控制加号部分
::v-deep.hide .el-upload--picture-card {
display: none;
}
// 去掉动画效果
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
transition: all 0s;
}
::v-deep .el-list-enter,
.el-list-leave-active {
opacity: 0;
transform: translateY(0);
}
.cropper {
height: 300px;
width: auto;
}
</style>
我这里是把el-upload
和vue-cropper
整合到一起封装成了一个组件, 然后在其他页面进行调用, 调用方式如下, 其中相关的不相关的代码就删掉了
<local-img-upload @input="handlerImgList" :value="form.imagesList"
@delete="handlerDeleteIds"></local-img-upload>
import LocalImgUpload from "@/views/components/LocalImgUpload";
export default {
components: {
LocalImgUpload,
},
data() {
return {
form: {},
fm: new FormData(),
**************
};
},
methods: {
handlerImgList(imgList) {
this.fm.delete("files");
imgList.forEach(item => this.fm.append("files", item));
},
handlerDeleteIds(deleteIds) {
this.fm.delete("deleteIds");
this.fm.append("deleteIds", deleteIds);
},
**************
}
**************
}
评论区