首先说下业务场景:
业务当中需要上传一张大图,然后在大图上面截取一小部分图片作为target图片,在截图的同时获取到截取图片区域对应在大图上的坐标区域。
如果你的vue版本时3.0以上,代码可直接使用
<script setup>
import {onMounted, ref, watch, nextTick, reactive} from 'vue';
import {getCurrentInstance} from '@vue/runtime-core'
import {ElMessage} from 'element-plus';
import axios from '../http/axios';
import html2canvas from "html2canvas";
import 'vue-cropper/dist/index.css'
import { VueCropper } from "vue-cropper";
const currentInstance = ref()
const props = defineProps({
caseId: Number,
projectId: Number,
platform: Number,
imageUrl: String,
stepId: Number,
});
//截图位置对应原图的坐标区域
const selectRegion = ref()
const option = ref({
info: true, // 裁剪框的大小信息
outputSize: 1, // 裁剪生成图片的质量
outputType: "jpeg", // 裁剪生成图片的格式
canScale: true, // 图片是否允许滚轮缩放
autoCrop: false, // 是否默认生成截图框
fixedBox: false, // 固定截图框大小 不允许改变
fixed: false, // 是否开启截图框宽高固定比例
fixedNumber: [1, 1], // 截图框的宽高比例
full: false, // 是否输出原图比例的截图
canMove: false, //是否可以移动原图
canMoveBox: false, // 截图框能否拖动
original: false, // 上传图片按照原始比例渲染
centerBox: true, // 截图框是否被限制在图片里面
infoTrue: true, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
mode: "1073px 589px" //图片默认渲染方式
})
const uploadImg = ref('')
const show = ref(false)
const image = ref({
url: props.imageUrl
});
const elements = ref({
id: null,
eleName: '',
eleType: "image",
eleValue: '',
projectId: props.projectId,
eleDeviceModel: "",
eleAppName: "",
eleGuiName: "",
remark: '',//此处传递控件元素区域为了方便后期的取值,这样不需要修改数据库表格
})
//裁剪
const tailoring = () => {
nextTick(() => {
html2canvas(document.getElementById('place'), {}).then(canvas => {
let dataURL = canvas.toDataURL("image/png");
uploadImg.value = dataURL
show.value = true
});
})
}
//保存截图
const save = () => {
/**
* 下面这行代码在vue2中使用
* this.$refs.cropper.getCropData
*
**/
//获取截图的base64格式数据
currentInstance.value.refs.cropper.getCropData((data) => {
show.value = false
//可以在此处对文件进行上传或者其他处理
})
//获取截图的Blob格式数据
currentInstance.value.refs.cropper.getCropBlob(data => {
show.value = false
let formData = new FormData();
let fileName = guid();
formData.append("file", blobToFile(data, fileName + ".png"));
formData.append("type", 'keepFiles');
//1.先上传截取到的小图
axios
.post("/folder/upload", formData, {headers: {"Content-type": "multipart/form-data"}})
.then((resp) => {
if (resp['code'] === 2000) {
//2.图片上传完成以后,根据返回值创建一个控件元素
elements.value.eleName = "PlaceAssert-Ele_" + guid()
elements.value.eleValue = resp['data']
elements.value.remark = JSON.stringify(selectRegion.value)
const transfer = {
id: null,
projectId: '',
addTime: '',
caseId: '',
originalPath: image.value.url,
elementValue: resp['data']
}
axios.put("/controller/transfer", transfer).then(response => {
if (response['code'] === 2000) {
axios.put("/controller/elements", elements.value).then(eleResp => {
if (eleResp['code'] === 2000) {
ElMessage.success({
message: eleResp['message'],
});
} else {
ElMessage.error({
message: eleResp['message'],
});
}
})
}
})
} else {
ElMessage.error({
message: resp['message']
})
}
});
});
}
/**
* 随机生成uuid
* */
const guid = () => {
return 'xxxxx-xxxx-4xxx-yxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
//取消
const close = () => {
show.value = false
}
//将blob转换为file
const blobToFile = (theBlob, fileName) => {
theBlob.lastModifiedDate = new Date(); // 文件最后的修改日期
theBlob.name = fileName; // 文件名
return new File([theBlob], fileName, {type: theBlob.type, lastModified: Date.now()});
}
const mouseup = (e) => {
selectRegion.value.startX = e.offsetX
selectRegion.value.startY = e.offsetY
}
const mousedown = (e) => {
selectRegion.value.endX = e.offsetX
selectRegion.value.endY = e.offsetY
}
const mouseenterEnter = (e) => {
if (uploadImg.value == "") {
return;
}
//开始裁剪
currentInstance.value.refs.cropper.startCrop();
/**
* 上面这行代码在 vue2 中使用方法
* this.$refs.cropper.startCrop()
* */
}
// const cropper = ref({})
const mouseenterLeave = (e) => {
//停止裁剪
// currentInstance.value.ctx.$refs.cropper.stopCrop();
currentInstance.value.refs.cropper.stopCrop();
//获取截图框基于容器的坐标点
// selectRegion.value = currentInstance.value.ctx.$refs.cropper.getCropAxis()
selectRegion.value = currentInstance.value.refs.cropper.getCropAxis()
/**
* 上面这行代码在 vue2 中使用方法
* this.$refs.cropper.stopCrop()
* */
}
onMounted(() => {
/**
* 此处这样使用时因为Vue3不同于Vue2,在 Vue3的setup中我们是无法访问到this的,所以我们需要借助一个方法,
* 那就是getCurrentInstance,该方法返回了当前的实例对象
*
* 注意!!!!!
* 不要把该函数当作是optionsApi中来获取 this 使用。
* 该方法只在 setup、生命周期函数中有效,在方法中是无效的
* */
currentInstance.value = getCurrentInstance()
});
</script>
<style>
.preview {
width: auto;
height: auto;
position: absolute;
top: 0;
left: 0;
border: 1px dashed #e6a23c;
background-color: #000000;
}
</style>
<template>
<el-image id="place" v-model="image" style="width: 100%; height: 100%" :src="image.url"></el-image>
<div>
<el-button @click="tailoring" type="primary"
><i class="el-icon-upload el-icon--right">裁剪</i
></el-button>
<!--继续写页面的其他内容 pop_alert可封装成组件使用-->
<!--将生成的图片进行拖动截图-->
<div class="preview" v-if="show">
<vueCropper
@mouseenter="mouseenterEnter"
@mouseleave="mouseenterLeave"
ref="cropper"
:img="uploadImg"
:outputSize="option.size"
:outputType="option.outputType"
:info="true"
:full="option.full"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:original="option.original"
:autoCrop="option.autoCrop"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
:centerBox="option.centerBox"
:infoTrue="option.infoTrue"
:fixedBox="option.fixedBox"
:mode="option.mode"
style="background-image:none; width: 1151px; height: 707px"
></vueCropper>
<el-row>
<el-button @click="save" type="primary"
><i class="el-icon-upload el-icon--right"></i
>确认截图
</el-button>
<br/>
<el-button @click="close" type="danger" icon="el-icon-delete">取消</el-button>
</el-row>
</div>
</div>
</template>
目前网络上查找了很多资料,大都是支持Vue2的代码,对于最新版本的vue非常不友好,经过各种踩坑,才有了以上结果
注意⚠️,通过<script setup>
语法糖的写法,其组件是默认关闭的,也就是说如果是通过$refs
或者$parents
来访问子组件中定义的值是拿不到的,必须通过defineExpose向外暴露你想获取的值才行。
更多详细资料,请关注官方信息GitHub - xyxiao001/vue-cropper: A simple picture clipping plugin for vue