效果:
- 上传:
- 裁剪:
- 点击图片预览效果:
一、需要安装的插件:
1.1 图片放大预览插件:v-viewer
npm install v-viewer --save
1.2 图片裁剪插件:vue-cropper
npm install vue-cropper
或 yarn add vue-cropper
二、组件:
2.1 图片裁剪弹窗组件:components -> cropper -> index.vue
<template>
<!-- 图片裁剪弹窗-组件 -->
<div class="cropper_model">
<el-dialog
title="图片剪裁"
width="700px"
top="50px"
class="cropper_model_dlg"
:visible.sync="dialogVisible"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="cropper_content">
<div class="cropper" style="text-align: center">
<vueCropper
ref="cropper"
:img="options.img"
:outputSize="options.outputSize"
:outputType="options.outputType"
:info="options.info"
:canScale="options.canScale"
:autoCrop="options.autoCrop"
:autoCropWidth="options.autoCropWidth"
:autoCropHeight="options.autoCropHeight"
:fixed="options.fixed"
:fixedBox="options.fixedBox"
:fixedNumber="options.fixedNumber"
@realTime="previewImg"
>
</vueCropper>
<div class="cropper_btns">
<el-button type="blue" @click="goUpload" size="mini">
重新上传
</el-button>
<el-button
@click="rotateLeft"
icon="el-icon-refresh-left"
size="mini"
title="左旋转"
>
</el-button>
<el-button
@click="rotateRight"
icon="el-icon-refresh-right"
size="mini"
title="右旋转"
>
</el-button>
<el-button @click="changeScale(1)" size="mini" title="放大">
+
</el-button>
<el-button @click="changeScale(-1)" size="mini" title="缩小">
-
</el-button>
<el-button
type="primary"
size="mini"
@click="uploadImg"
:loading="loading"
>
确定上传
</el-button>
</div>
</div>
<!-- <div class="cropper_right">
<h3>预览</h3>
<div
class="cropper_preview"
:style="{
width: preview.w + 'px',
height: preview.h + 'px',
overflow: 'hidden',
margin: '5px',
}"
>
<div :style="preview.div">
<img :src="preview.url" :style="preview.img" alt="" />
</div>
</div>
<div style="margin-top: 100px">
<el-button type="primary" @click="uploadImg" :loading="loading">
确定上传
</el-button>
</div>
</div> -->
</div>
<!-- <div slot="footer" class="dialog-footer">
<el-button @click="downLoad('blob')">下载</el-button>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="uploadImg" :loading="loading">
确认
</el-button>
</div> -->
</el-dialog>
</div>
</template>
<script>
import { VueCropper } from "vue-cropper";
export default {
components: { VueCropper },
props: {
imgCropperOptions: {},
},
data() {
return {
dialogVisible: false,
loading: false,
options: {
img: "", // 裁剪图片的地址
outputSize: 1, // 裁剪生成图片的质量
outputType: "png", // 裁剪生成图片的格式
info: true, // 裁剪框的大小信息
canScale: true, // 图片是否允许滚动缩放
autoCrop: true, // 是否默认生成截图狂
autoCropWidth: 100, // 默认生成截图框宽度
autoCropHeight: 100, // 默认生成截图框高度
fixed: true, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
full: true, // 是否输出原图比例的截图
fixedBox: true, // 固定截图框大小 不允许改变
canMove: true, // 上传图片是否可以移动
canMoveBox: true, // 截图框能否拖动
original: true, // 上传图片按照原始比例渲染
centerBox: false, // 截图框是否被限制在图片里面
high: false, // 是否按照设备的dpr输出等比例图片
infoTrue: true, // true为展示真实输出图片宽高false展示看到的截图框宽高
maximgSize: 150, // 限制图片最大宽度和高度
enlarge: 1, // 图片根据截图框输出比例倍数
mode: "contain", // 图片默认渲染方式(contain, cover, 100px, 100% auto)
},
preview: {},
};
},
methods: {
open(data) {
console.log("open-data", data);
this.options.img = window.URL.createObjectURL(data);
this.dialogVisible = true;
console.log("this.imgCropperOptions", this.imgCropperOptions);
if (this.imgCropperOptions) {
this.options.autoCropWidth = this.imgCropperOptions.autoCropWidth;
this.options.autoCropHeight = this.imgCropperOptions.autoCropWidth;
this.options.maximgSize = this.imgCropperOptions.maximgSize;
}
},
close() {
this.dialogVisible = false;
},
// base64转图片文件
dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(",");
let mime = arr[0].match(/:(.*?);/)[1];
let bstr = atob(arr[1]);
let len = bstr.length;
let u8arr = new Uint8Array(len);
while (len--) {
u8arr[len] = bstr.charCodeAt(len);
}
return new File([u8arr], filename, { type: mime });
},
downLoad(type) {
event.preventDefault();
const aLink = document.createElement("a");
if (type === "blob") {
this.$refs.cropper.getCropBlob((data) => {
let downImg = window.URL.createObjectURL(data);
aLink.download = "photo.png";
aLink.href = downImg;
aLink.click();
});
} else {
this.$refs.cropper.getCropData((data) => {
let file = this.dataURLtoFile(data, "test");
aLink.href = file;
aLink.click();
});
}
},
// 左旋转
rotateLeft() {
this.$refs.cropper.rotateLeft();
},
// 右旋转
rotateRight() {
this.$refs.cropper.rotateRight();
},
// 放大缩小
changeScale(num) {
num = num || 1;
this.$refs.cropper.changeScale(num);
},
// 实时预览
previewImg(data) {
//console.log('data', data);
this.preview = data;
//if(this.preview.img)
},
goUpload() {
this.$emit("upAgain");
},
// 上传图片
uploadImg() {
let self = this;
//self.loading = true;
this.$refs.cropper.getCropData((data) => {
let file = this.dataURLtoFile(data, "photo.png");
// 生成文件类型
//self.loading = false;
this.$emit("getFile", file);
});
},
//自定义上传,裁剪后调用
},
};
</script>
<style lang="scss" scoped>
.el-dialog__wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.cropper_model_dlg {
.cropper_content {
margin: 0 auto;
width: 650px;
height: 650px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.cropper {
width: 600px;
height: 600px;
background: yellow;
}
.cropper_right {
width: 300px;
text-align: center;
}
.cropper_preview {
margin: 0 auto;
display: inline-block;
border: 1px solid #ddd;
}
.cropper_btns {
margin-top: 20px;
}
.previewImg {
width: 100%;
height: 100%;
}
}
</style>
2.2 图片拖拽交换位置组件:components -> dragImgItem -> index.vue
<template>
<!-- 图片拖拽交换位置组件 -->
<div
class="imgItem"
@dragstart="dragStartHandler"
@dragover.prevent="dragOverHandler"
@dragenter="dragEnterHandler"
@dragend="dragEndHandler"
:style="{
width: imgWidth,
height: imgHeight,
}"
>
<img
class="bgImg"
:src="imgSrc"
alt=""
:width="imgWidth"
:height="imgHeight"
/>
<em class="imgClose" @click="removeImg"></em>
</div>
</template>
<script>
export default {
name: "dragImgItem",
props: {
//图片对应的下标
index: {
type: Number,
required: true,
},
//图片回显的路径
imgSrc: {
type: String,
required: true,
},
//图片回显的宽度
imgWidth: {
default: "150px",
},
//图片回显的高度
imgHeight: {
default: "150px",
},
},
methods: {
// 用户开始拖动元素时触发
dragStartHandler() {
this.$emit("setCurDragImg", this.index);
},
//用户完成元素拖动后触发
dragEndHandler(e) {
this.$emit("clearCurDragImg");
},
// 当某被拖动的对象在另一对象容器范围内拖动时触发此事件
dragOverHandler(e) {
e.dataTransfer.dropEffect = "move";
},
// 当被鼠标拖动的对象进入其容器范围内时触发此事件
dragEnterHandler(e) {
console.log("e", e);
e.dataTransfer.effectAllowed = "move";
this.$emit("dragImg", this.index);
},
//删除图片事件
removeImg() {
this.$emit("removeImg");
},
},
};
</script>
<style scoped lang="scss">
.imgItem {
position: relative;
height: 150px;
width: 150px;
display: inline-block;
border-radius: 5px;
float: left;
margin: 0 15px 15px 0;
.bgImg {
height: 150px;
width: 150px;
object-fit: cover;
border: 1px solid #dcdfe6;
border-radius: 5px;
}
.imgClose {
position: absolute;
top: 0;
right: 0;
width: 30px;
height: 30px;
margin-top: -13px;
margin-right: -13px;
cursor: pointer;
background: url("~@/assets/images/imgClose.png") top left
no-repeat;
background-size: cover;
}
}
</style>
三、在页面中上传图片
<template>
<div class="imgUploadDemo">
<el-form
:model="form"
ref="ruleForm"
label-width="180px"
class="form"
size="small"
>
<el-form-item label="商品列表封面图:" prop="default_image">
<div class="imgUpload">
<el-upload
ref="defaultImgUpload"
action
:auto-upload="false"
:show-file-list="false"
list-type="picture-card"
:on-change="handleDefaultSuccess"
>
<img
v-if="form.default_image"
:src="form.default_image"
class="defaultImg"
/>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<div class="imgSize">
(建议尺寸为220像素*220像素长宽比例1:1的图片)
</div>
</div>
</el-form-item>
<el-form-item label="商户详情页头部轮播图:" prop="swiperList">
<div class="imgUpload">
<div class="merchantDetailImg imgDiv">
<viewer :images="form.swiperList">
<dragImgItem
class="imgItem"
v-for="(item, index) of form.swiperList"
:key="index"
:index="index"
:imgSrc="item"
@setCurDragImg="setCurDragImg(index, 1)"
@clearCurDragImg="clearCurDragImg(1)"
@dragImg="dragSwiperImg(index, 1)"
@removeImg="removeSwiper(item)"
>
</dragImgItem>
</viewer>
</div>
<div class="merchantDetailUpload">
<el-upload
action
ref="upload"
:auto-upload="false"
:show-file-list="false"
list-type="picture-card"
:on-change="handleSwiperSuccess"
>
<i class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</div>
<div style="clear: both"></div>
<div class="imgSize">
(建议尺寸为600像素*600像素长宽比例1:1的图片),支持长按鼠标左键拖拽图片交换位置
</div>
</el-form-item>
</el-form>
<!-- 图片裁剪弹窗-start -->
<ImgCropper
ref="myCropper"
:imgCropperOptions="imgCropperOptions"
@getFile="getFile"
@upAgain="upAgain"
></ImgCropper>
<!-- 图片裁剪弹窗-end -->
</div>
</template>
<script>
import dragImgItem from "@/components/dragImgItem"; //图片拖拽交换位置组件
import ImgCropper from "@/components/cropper"; //图片裁剪弹窗
//图片预览插件
import Vue from "vue";
import Viewer from "v-viewer";
import "viewerjs/dist/viewer.css";
Vue.use(Viewer);
export default {
name: "imgUploadDemo",
components: {
dragImgItem,
ImgCropper,
},
data() {
return {
imgCropperOptions: {
autoCropWidth: 600, // 默认生成截图框宽度
autoCropHeight: 600, // 默认生成截图框高度
maximgSize: 600, // 限制图片最大宽度和高度
},
// 图片列表
form: {
default_image: "",
swiperList: [
"https://circle.sutpay.cn/youhuiquanyi/6083782831/goods_image463321.png",
"https://circle.sutpay.cn/youhuiquanyi/6083782831/goods_image296439.png",
"https://circle.sutpay.cn/youhuiquanyi/6083782831/goods_image144881.png",
"https://circle.sutpay.cn/youhuiquanyi/6083782831/goods_image098233.png",
],
},
curSwiperImgIndex: null, // 当前被拖动图片(商户详情页头部轮播图)的index
imgUploadType: 1,
};
},
methods: {
setCurDragImg(index, type) {
console.log("正在被拖动的图片-index", index);
this.curSwiperImgIndex = index;
},
clearCurDragImg(type) {
console.log("clearCurDragImg-type", type);
this.curSwiperImgIndex = null;
},
//当前拖拽的图片进入到要交互的图片位置时触发事件
dragSwiperImg(index) {
// 判断被拖拽的图片与撞击的图片是否相同(通过图片的下标判断)
if (index === this.curSwiperImgIndex) return;
let newList = [...this.form.swiperList];
let curImg = this.form.swiperList[this.curSwiperImgIndex]; // 当前拖动的图片
let hitImg = this.form.swiperList[index]; // 被撞击的图片
console.log("商户详情页头部轮播图-curImg", curImg);
console.log("商户详情页头部轮播图-hitImg", hitImg);
// 两张图片交换位置
if (curImg) {
newList.splice(this.curSwiperImgIndex, 1, hitImg);
newList.splice(index, 1, curImg);
// 修改当前拖动图片的index,此时已经变成被撞击图片的位置了
this.curSwiperImgIndex = index;
this.form.swiperList = newList;
console.log("this.form.swiperList", this.form.swiperList);
} else {
this.$message.error("仅支持同类型的的图片拖拽交换位置!");
}
},
//设置图片裁剪弹窗参数
setImgCropperOptions(size) {
console.log("设置图片裁剪弹窗参数size", size);
this.imgCropperOptions.autoCropWidth = size;
this.imgCropperOptions.autoCropHeight = size;
},
//上传商户详情头部轮播图
async handleSwiperSuccess(res, file) {
this.imgUploadType = 2;
await this.setImgCropperOptions(600);
this.$nextTick(() => {
this.$refs.myCropper.open(res.raw || res);
});
},
//上传封面图
async handleDefaultSuccess(res, file) {
this.imgUploadType = 1;
await this.setImgCropperOptions(220);
this.$nextTick(() => {
this.$refs.myCropper.open(res.raw || res);
});
},
//删除商户详情页头部轮播图
removeSwiper(itemImg) {
console.log("删除商品详情页头部轮播图-itemImg", itemImg);
this.form.swiperList = this.form.swiperList.filter(
(item, index) => {
return item != itemImg;
}
);
console.log("this.form.swiperList", this.form.swiperList);
},
// 点击弹框重新时触发
upAgain() {
if (this.imgUploadType == 1) {
this.$refs["defaultImgUpload"].$refs[
"upload-inner"
].handleClick();
} else if (this.imgUploadType == 2) {
this.$refs["upload"].$refs["upload-inner"].handleClick();
}
},
//裁剪弹窗-确定上传回调事件
getFile(file) {
//如果是想拿到本地的图片信息则可通过file拿到
console.log("file", file);
console.log("url", URL.createObjectURL(file));
if (this.imgUploadType == 1) {
this.form.default_image = URL.createObjectURL(file);
} else if (this.imgUploadType == 2) {
this.form.swiperList.push(URL.createObjectURL(file)); //本地的图片回显
}
//关闭裁剪弹窗
this.$refs.myCropper.close();
//如果是需要拿到图片上传到服务器上后的地址,就需要通过ajax请求
//this.uploadImage(file,this.imgUploadType);
},
//图片上传请求
uploadImage(res, imgUploadType) {
const isImgType =
res.type === "image/bmp" ||
"image/png" ||
"image/jpeg" ||
"image/jpg";
if (!isImgType) {
return this.$message.error(
"上传图片只能是bmp/image/jpeg/png/jpg格式!"
);
}
let formdata = new FormData();
formdata.append("img", res);
this.$refs.myCropper.loading = true;
this.$axios
.post(`/xx/upload_image/`, formdata)
.then((res) => {
if (imgUploadType == 1) {
this.form.default_image = res.img_id;
} else if (imgUploadType == 2) {
this.form.swiperList.push(res.img_id);
}
//关闭裁剪弹窗
this.$refs.myCropper.close();
})
.catch((err) => {
if (err.response) {
this.$message.error(
err.response.data.error || err.response.data.detail
);
}
})
.finally(() => {
this.$refs.myCropper.loading = false;
});
},
},
};
</script>
<style scoped lang="scss">
.imgUploadDemo {
padding: 100px;
.merchantDetailImg {
margin: 10px 0;
.imgItem {
position: relative;
height: 150px;
width: 150px;
display: inline-block;
border-radius: 5px;
float: left;
margin: 0 15px 15px 0;
.bgImg {
height: 150px;
width: 150px;
object-fit: cover;
border: 1px solid #dcdfe6;
border-radius: 5px;
}
.imgClose {
position: absolute;
top: 0;
right: 0;
width: 30px;
margin-top: -13px;
margin-right: -13px;
cursor: pointer;
}
}
}
.imgSize {
font-size: 12px;
line-height: 12px;
margin-top: 0;
float: left;
}
.defaultImg {
height: 150px;
width: 150px;
object-fit: cover;
border-radius: 5px;
}
}
.imgUploadDemo ::v-deep .el-upload--picture-card {
margin-bottom: 15px;
border: 1px solid #c0ccda;
border-radius: 5px;
overflow: hidden;
}
</style>