需求:
-
上传的图片限制长宽相同;
-
只能上传图片;
-
图片大小限制 500k
-
当前项目仅需要上传的图片信息
项目组件使用
裁剪:vue-cropper
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
上传: Upload 上传(element plus 组件)
实现理论:
- 通过组件 Upload 进行图片上传前校验方法判断是否需要裁剪、是否是图片、是否超过大小限制;
- 裁剪后的图片限制大小;
- 上传的图片信息暴漏出组件;
组件调用
img-url 默认预览的图片 @img-upload 上传所需的最终图片
代码实现
页面
裁剪时弹窗显示
<!-- 上传 -->
<el-upload
class="uploader_cutsom"
action=""
:show-file-list="false"
:accept="imgUpload.imgBmp"
:http-request="updataImg"
:before-upload="beforUpload"
>
<div v-if="imgUpload.imageUrl" class="w-full h-full relative">
<img class="w-full h-full" :src="imgUpload.imageUrl" />
<!-- 删除 -->
<div
@click="clearImg"
class="absolute top-0 right-0 w-8 h-8 rounded flex items-center justify-center bg-black bg-opacity-30 text-white text-base z-20 cursor-pointer"
>
<i class="iconfont icon-shanchu"></i>
</div>
</div>
<div v-else class="uploader_cutsom_default">
<p>从你的计算机中</p>
<p class="uploader_cutsom_default_button">选择文件</p>
</div>
</el-upload>
<!-- 裁剪 -->
<el-dialog
v-model="imgUpload.dialogCropping"
custom-class="dialog_custom"
title="裁剪图片"
append-to-body
:close-on-press-escape="false"
:show-close="false"
:close-on-click-modal="false"
>
<div>
<div class="h-96 w-full">
<vueCropper
ref="cropper"
class="vue_cropper_custom"
:img="imgCropping.imageUrl"
:outputType="imgCropping.outputType"
:autoCrop="imgCropping.autoCrop"
:autoCropWidth="imgCropping.autoCropWidth"
:autoCropHeight="imgCropping.autoCropHeight"
:fixed="imgCropping.fixed"
:fixedNumber="imgCropping.fixedNumber"
:centerBox="imgCropping.centerBox"
:full="imgCropping.full"
></vueCropper>
</div>
</div>
<template #footer>
<el-button v-focus @click="handleCropping($refs.cropper, false)">
取消
</el-button>
<el-button
v-focus
type="primary"
@click="handleCropping($refs.cropper, true)"
>
确定
</el-button>
</template>
</el-dialog>
逻辑
/** 图片地址传递 */
const prop = defineProps({
imgUrl: {
type: String,
default: "",
},
});
/** 事件通知 */
const emit = defineEmits(["img-upload"]);
/** 图片上传 */
const imgUpload = reactive({
// 是否展示裁剪
dialogCropping: false,
isCropping: false, // 判断是否已经截图
// 图片
imageUrl: "",
// 图片格式
imgBmp: "image/*",
// 图片名称
imgName: "",
});
// 图片上传前校验
const beforUpload: UploadProps["beforeUpload"] = async (rawFile) => {
// 判断是否需要进行裁剪
imgUpload.isCropping = (await imgSize(rawFile)) as boolean;
// 图片名称
imgUpload.imgName = rawFile.name;
if (imgUpload.isCropping) {
if (rawFile.type.indexOf("image/") === -1) {
ElMessage.error("请上传正确的图片格式");
return false;
} else if (rawFile.size / 1024 / 1024 > 0.5) {
ElMessage.error("图片大小不能超过 500k");
return false;
}
} else {
// 进入裁剪
imgCropping.imageUrl = URL.createObjectURL(rawFile);
imgUpload.dialogCropping = true;
return false;
}
};
// 上传
const updataImg = async (data: any) => {
// 如果未截图,打开裁剪
if (imgUpload.isCropping) {
// 图片预览
imgUpload.imageUrl = URL.createObjectURL(data.file);
imgUpload.dialogCropping = false;
imgUpload.isCropping = false;
// 图片信息
emit("img-upload", data.file);
}
};
/** 图片裁剪 */
const imgCropping = reactive({
imageUrl: "",
// 裁剪生成图片的格式
outputType: "png",
// 是否默认生成截图框
autoCrop: true,
// 上传图片按照原始比例渲染
// original: true,
// 是否输出原图比例的截图
full: false,
// 默认生成截图框宽度
autoCropWidth: 160,
// 默认生成截图框高度
autoCropHeight: 160,
// 是否开启截图框宽高固定比例
fixed: true,
// 截图框的宽高比例
fixedNumber: [1, 1],
// 截图框是否被限制在图片里面
centerBox: true,
});
// 生成裁剪图片
const handleCropping = (roleRefs: any, type: boolean) => {
if (type) {
roleRefs.getCropBlob((data: any) => {
// 判断裁剪图片大小
if (data.size / 1024 / 1024 > 0.5) {
ElMessage.error("裁剪图片大小不能超过 500k");
} else {
// 图片预览
imgUpload.imageUrl = URL.createObjectURL(data);
imgUpload.dialogCropping = false;
// 图片信息
emit("img-upload", blobToFile(data, imgUpload.imgName));
}
});
} else {
imgUpload.imageUrl = "";
imgUpload.dialogCropping = false;
}
};
// 清除图片
const clearImg = () => {
imgUpload.imageUrl = "";
emit("img-upload");
};
onMounted(() => {
// 图片地址传递
imgUpload.imageUrl = prop.imgUrl;
});
图片相关方法封装
/** 查询图片大小 */
export const imgSize = (file: any) => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
// 让页面中的img标签的src指向读取的路径
let img = new Image();
img.src = reader.result as string;
if (img.complete) {
// 如果存在浏览器缓存中
if (img.width / img.height !== 1) {
resolve(false);
} else {
resolve(true);
}
} else {
img.onload = () => {
if (img.width / img.height !== 1) {
resolve(false);
} else {
resolve(true);
}
};
}
};
});
};
/**
* 文件格式转换
* @description blobToFile
* @param {Blob} blob
* @param {String} fileName
*/
export const blobToFile = (blob: any, fileName: string) => {
return new window.File([blob], fileName, {
type: blob.type,
});
};