一、引入 Vue Cropper
1、Vue 3
组件内引入
npm install vue-cropper@next
import 'vue-cropper/dist/index.css'
import { VueCropper } from "vue-cropper";
2、Vue3
全局引入
import VueCropper from 'vue-cropper';
import 'vue-cropper/dist/index.css'
const app = createApp(App)
app.use(VueCropper)
app.mount('#app')
二、封装组件(局部引用弹框实现)
1、 index.vue
相关功能:1、上传图片地址,相关服务器地址自行配置
2、裁剪尺寸自定义
3、截取图片实时回显及编辑回显
4、目前根据项目需求写的固定尺寸,其他相关配置可查阅文档
<template>
<div class="cropper-content">
<div class="cropper-box">
<div class="cropper">
<vue-cropper
ref="cropper"
:img="option.img"
:outputSize="option.outputSize"
:outputType="option.outputType"
:info="option.info"
:canScale="option.canScale"
:autoCrop="option.autoCrop"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
:full="option.full"
:fixedBox="option.fixedBox"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:original="option.original"
:centerBox="option.centerBox"
:height="option.height"
:infoTrue="option.infoTrue"
:maxImgSize="option.maxImgSize"
:enlarge="option.enlarge"
:mode="option.mode"
@realTime="realTime"
@imgLoad="imgLoad"
>
</vue-cropper>
</div>
<!--底部操作工具按钮-->
<div class="footer-btn">
<div class="scope-btn">
<label class="btn" for="uploads">选择封面</label>
<input
type="file"
id="uploads"
style="position: absolute; clip: rect(0 0 0 0)"
accept="image/png, image/jpeg, image/gif, image/jpg"
@change="selectImg($event)"
/>
<a-space>
<a-button size="small" type="outline" status="danger" @click="changeScale(1)">放大</a-button>
<a-button size="small" type="outline" status="danger" @click="changeScale(-1)">缩小</a-button>
<a-button size="small" type="outline" status="danger" @click="rotateLeft">↺ 左旋转</a-button>
<a-button size="small" type="outline" status="danger" @click="rotateRight">↻ 右旋转</a-button>
</a-space>
</div>
<div class="upload-btn">
<a-button size="small" type="primary" status="success" @click="uploadImg('blob')">保存封面 <i class="el-icon-upload"></i></a-button>
</div>
</div>
</div>
<!--预览效果图-->
<div class="show-preview">
<div
v-if="previews.url == '' && imgCropper == ''"
:style="{ width: option.autoCropWidth + 'px', height: option.autoCropHeight + 'px' }"
class="noPreview"
></div>
<div v-else :style="previews.div" class="preview">
<img :src="previews.url" :style="previews.img" />
</div>
<div v-if="imgCropper && previews.url == ''">
<img :src="baseUrl + imgCropper" />
</div>
<div style="margin-top: 10px; color: #4e5969; font-size: 16px">固定尺寸:{{ option.autoCropWidth }}*{{ option.autoCropHeight }}px</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, defineEmits, defineProps, defineExpose, watch, onMounted } from 'vue';
import { Message, Modal } from '@arco-design/web-vue';
import { getToken } from '@/utils/auth';
import useLoading from '@/hooks/loading';
const baseUrl = import.meta.env.VITE_API_BASE_URL;
const { loading, setLoading } = useLoading();
// import 'vue-cropper/dist/index.css'
import { VueCropper } from 'vue-cropper';
import { uploadFile } from '@/api/file';
import { getAllClassify } from '@/api/tagManage/tagManage';
const emit = defineEmits(['uploadImgSuccess']);
onMounted(() => {
setCropSize();
});
// 图片接口路径
const uploadUrl = ref('');
uploadUrl.value = import.meta.env.VITE_API_BASE_URL + '/file/upload';
const previews = reactive({});
const option = reactive({
img: '', //裁剪图片的地址
autoCrop: true, // 是否默认生成截图框
autoCropWidth: '', // 默认生成截图框宽度
autoCropHeight: '', // 默认生成截图框高度
fixedBox: true, // 固定截图框大小 不允许改变
canMove: true, // 上传图片是否可以移动
});
// const option = reactive({
// // img: 'https://avatars2.githubusercontent.com/u/15681693?s=460&v=4',
// img: '',
// outputSize: 1, // 裁剪生成图片的质量
// outputType: 'png', // 裁剪生成图片的格式
// info: true, // 裁剪框的大小信息
// canScale: true, // 图片是否允许滚动缩放
// autoCrop: true, // 是否默认生成截图狂
// autoCropWidth: 320, // 默认生成截图框宽度
// autoCropHeight: 440, // 默认生成截图框高度
// fixed: true, // 是否开启截图框宽高固定比例
// fixedNumber: [8, 11], // 截图框的宽高比例
// full: true, // 是否输出原图比例的截图
// fixedBox: false, // 固定截图框大小 不允许改变
// canMove: true, // 上传图片是否可以移动
// canMoveBox: true, // 截图框能否拖动
// original: false, // 上传图片按照原始比例渲染
// centerBox: true, // 截图框是否被限制在图片里面
// high: false, // 是否按照设备的dpr输出等比例图片
// infoTrue: true, // true为展示真实输出图片宽高false展示看到的截图框宽高
// maximgSize: 100, // 限制图片最大宽度和高度
// enlarge: 1, // 图片根据截图框输出比例倍数
// mode: 'contain', // 图片默认渲染方式(contain, cover, 100px, 100% auto)
// })
// props相关类型
const props = defineProps({
name: {
type: String,
default: '',
},
img: {
type: String,
default: '',
},
imgCropper: {
type: String,
default: '',
},
bannerType: {
type: Number,
default: 6,
},
});
//初始化函数
const imgLoad = (msg) => {
console.log('工具初始化函数=====' + msg);
};
const cropper = ref('');
//图片缩放
const changeScale = (num) => {
num = num || 1;
cropper.value.changeScale(num);
};
//向左旋转
const rotateLeft = () => {
cropper.value.rotateLeft();
};
//向右旋转
const rotateRight = () => {
cropper.value.rotateRight();
};
//实时预览函数
const realTime = (data) => {
Object.assign(previews, data);
};
//选择图片
const selectImg = (e) => {
let file = e.target.files[0];
if (e.target.value !== '') {
if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {
Message.error('图片类型要求:jpeg、jpg、png');
return false;
}
} else {
return false;
}
//转化为blob
let reader = new FileReader();
reader.onload = (e) => {
let data;
if (typeof e.target.result === 'object') {
data = window.URL.createObjectURL(new Blob([e.target.result]));
} else {
data = e.target.result;
}
option.img = data;
};
//转化为base64
reader.readAsDataURL(file);
};
//上传图片
const uploadImg = (type) => {
if (type === 'blob') {
//获取截图的blob数据
cropper.value.getCropBlob(async (data) => {
let formData = new FormData();
formData.append('files', data, 'DX.jpg');
//调用axios上传
let res = await uploadFile(formData);
if (res.code == 0) {
Message.success('截取封面成功');
let imgInfo = {
name: props.name,
url: res.data[0].filePath,
};
emit('uploadImgSuccess', imgInfo);
} else {
Message.error('文件服务异常,请联系管理员!');
}
});
}
};
const previewView = () => {
let img = new Image();
img.src = baseUrl + props.imgCropper;
img.onload = () => {
// 转化为base64
let canvas = document.createElement('canvas');
canvas.height = img.height;
canvas.width = img.width;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height);
let dataURL = canvas.toBlob(
(blob) => {
console.log('blob', URL.createObjectURL(blob));
option.img = URL.createObjectURL(blob);
},
'image/jpeg',
0.8
);
};
};
watch(
() => props.imgCropper,
(val) => {
if (val) {
console.log(val, '>>>>>>>>>>>>>>>>>>>>>>');
previewView();
}
},
{ immediate: true }
);
// 回显
// 根据类型设置裁剪框大小
const setCropSize = () => {
if (props.bannerType === 1 || props.bannerType === 4) {
option.autoCropWidth = 375;
option.autoCropHeight = 200;
} else if (props.bannerType === 2 || props.bannerType === 3 || props.bannerType === 5) {
option.autoCropWidth = 343;
option.autoCropHeight = 80;
} else if (props.bannerType === 6) {
option.autoCropWidth = 300;
option.autoCropHeight = 400;
}
};
</script>
<style>
@import 'vue-cropper/dist/index.css';
</style>
<style scoped lang="less">
.cropper-content {
display: flex;
display: -webkit-flex;
justify-content: space-between;
.cropper-box {
flex: 1;
width: 100%;
.cropper {
width: auto;
height: 440px;
}
}
.show-preview {
display: flex;
display: -webkit-flex;
-webkit-flex: 1;
flex-direction: column;
margin-left: 60px;
//flex: 1;
//justify-content: center;
.preview {
overflow: hidden;
background: #fff;
border: 1px solid #67c23a;
}
.noPreview {
width: 300px;
height: 400px;
background-color: #fff;
border: 1px solid #67c23a;
}
}
}
.footer-btn {
display: flex;
display: -webkit-flex;
justify-content: flex-end;
margin-top: 30px;
.scope-btn {
display: flex;
display: -webkit-flex;
justify-content: space-between;
padding-right: 10px;
}
.upload-btn {
display: flex;
display: -webkit-flex;
-webkit-flex: 1;
flex: 1;
justify-content: center;
}
.btn {
display: inline-block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin-right: 10px;
padding: 6px 15px;
color: #fff;
font-weight: 500;
font-size: 14px;
line-height: 1;
white-space: nowrap;
text-align: center;
background-color: #2a8dff;
border-color: #2a8dff;
border-radius: 3px;
outline: none;
outline: 0;
cursor: pointer;
-webkit-transition: 0.1s;
transition: 0.1s;
-webkit-appearance: none;
}
}
</style>
三、组件使用
1、封装的bl-form表单
<template #advertisingPicture>
<a-form-item v-if="route.query.type === 'detail'" label="广告图片 :" :rules="[{ required: true, message: '广告图片不能为空' }]">
<div class="image_box1">
<a-image width="80px" height="80px" :src="baseUrl + formData.advertisingPicture" />
</div>
<template #extra>支持格式:.PNG .JPEG .JPG ,仅支持上传单张</template>
</a-form-item>
<a-form-item v-else field="advertisingPicture" label="广告图片 :" :rules="[{ required: true, message: '广告图片不能为空' }]">
<div v-if="formData.advertisingPicture !== ''" class="image_box">
<img class="img_class" :src="baseUrl + formData.advertisingPicture" @click="uploadPicture('flagImg')" />
</div>
<div v-else class="upload-btn" @click="uploadPicture('flagImg')">
<div class="icon_class">
<icon-plus />
</div>
</div>
<template #extra>支持格式:.PNG .JPEG .JPG ,仅支持上传单张</template>
</a-form-item>
</template>
2、相关方法实现
// **************************裁剪图片****************************************
const cropPictureModal = reactive({
visible: false,
title: '修改资讯封面图',
});
//图片上传成功后
const handleUploadSuccess = (data: any) => {
console.log(data);
switch (data.name) {
case 'flagImg':
formData.advertisingPicture = data.url;
break;
}
cropPictureModal.visible = false;
};
const cropperName = ref('');
const bannerTypeValue = ref('');
const uploadPicture = (name: any) => {
if (formData.advertisingLocation) {
bannerTypeValue.value = formData.advertisingLocation;
} else {
Message.error('请先选择广告位置');
return false;
}
cropperName.value = name;
cropPictureModal.visible = true;
// 根据formData.advertisingLocation的值修改裁剪图片的title
const locationOptions = {
'1': '修改首页轮播图',
'2': '修改首页banner',
'3': '修改线上问诊banner',
'4': '修改在线商城轮播图',
'5': '修改在线商城banner',
};
const { advertisingLocation } = formData;
const title = locationOptions[advertisingLocation] || '';
cropPictureModal.title = title;
};
const cancelPictureModal = () => {
console.log('hhhhhh');
cropPictureModal.visible = false;
};
如图所示:裁剪广告图片类型为bannerType值为1,375*200