vue3+element-plus+vue-cropper实现裁剪图片上传

1.vue3+element-plus+vue-cropper实现裁剪图片

  • element-UI官网
  • element-plus官网
  • vue-cropper
  • vue3使用vue-cropper安装:npm install vue-cropper@next

2.vue-cropper插件:

 <vue-cropper :img="option.img" /><script setup>import {reactive} from "vue";// 裁剪的配置const option = reactive({img:'https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500'})</script>

3.效果图:

在这里插入图片描述

4.实现cropperUpload组件:

<template><div class="uploadMian"><div class="img-item" v-for="(item, index) in fileList" :key="index"><img :src="item.src" /><el-icon class="uploader-close" @click="delFn(index)"><Close /></el-icon><div v-if="item.isSuccess" class="uploader-Check"><el-icon ><Check /></el-icon></div><div class="button-div" v-if="item.file && isCropper"><el-button type="success" @click="uploadFileFn(item, index)">上传</el-button><el-button type="primary" @click="cropperFn(item, index)">裁剪</el-button></div></div><el-uploadv-if="multiple || (!multiple && fileList.length == 0)"class="avatar-uploader"action="#":accept="acceptArray.length > 0? acceptArray.map((n) => acceptType[n]).join(','): '*'":http-request="!isCropper ? uploadFileFn : () => {}":multiple="multiple":show-file-list="false":before-upload="beforeAvatarUpload"><el-icon class="avatar-uploader-icon"><Plus /></el-icon></el-upload></div><el-dialog title="裁切图片" v-model="showCropper" width="550px"><div class="cropper-content"><div class="cropper-box"><div class="cropper"><vue-cropperref="cropperRefs":img="option.img":output-size="option.outputSize":info="option.info":can-scale="option.canScale":auto-crop="option.autoCrop":auto-crop-width="option.autoCropWidth":auto-crop-height="option.autoCropHeight":fixed="option.fixed":fixed-number="option.fixedNumber":full="option.full":fixed-box="option.fixedBox":can-move="option.canMove":can-move-box="option.canMoveBox":original="option.original":center-box="option.centerBox":height="option.height":info-true="option.infoTrue":max-img-size="option.maxImgSize":enlarge="option.enlarge":mode="option.mode":limit-min-size="option.limitMinSize"/></div></div></div><span slot="footer"><div class="dialog-footer"><el-button @click="showCropper = false">取 消</el-button><el-button type="primary" @click="onSubmit">确 定</el-button></div></span></el-dialog>
</template><script setup>
import { ref, reactive, watch } from "vue";
import { Plus, Close,Check } from "@element-plus/icons-vue";
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
const props = defineProps({// 额外值otherData: {type: Object,default: () => {},},// 请求头headers: {type: Object,default: () => {},},//  参数值modelValue: {type: Array,default: () => {return [];},},// 多选multiple: {type: Boolean,default: false,},//   大小限制:10 * 1024 * 1024 = 10MBsize: {type: Number,default: 10 * 1024 * 1024,},// 是否需要裁剪isCropper: {type: Boolean,default: true,},// 请求的urlsendUrl: {type: String,default: "",},
});
const emits = defineEmits(["update:modelValue"]);
const cropperRefs = ref();
const cropperCb = ref(null);
const showCropper = ref(false);
let fileList = reactive([]);
const acceptArray = reactive(["png", "jpg", "jpeg"]); //选择类型
const acceptType = reactive({doc: "application/msword",docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",ppt: "application/vnd.ms-powerpoint",pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",xls: "application/vnd.ms-excel",xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",pdf: "application/pdf",csv: ".csv",txt: "text/plain",image: "image/*",png: "image/png",gif: "image/gif",jpg: "image/jpg",jpeg: "image/jpeg",
});// 监听传入的值
watch(props.modelValue,(value) => {const valueList = value || [];let newFileList = [];valueList.forEach((item) => {const indexThis=fileList.findIndex(n=>n.src==item)if(indexThis==-1){newFileList.push({src: item,isSuccess: true,});}});fileList.unshift(...newFileList);},{ immediate: true, deep: true }
);// 监听当前页面的fileList
watch(fileList,(value) => {const valueList = value.map((n) => {if (n.isSuccess) {return n.src;}return null;}).filter((n) => n != null);emits("update:modelValue", valueList);},{ deep: true }
);// 裁剪的配置
const option = reactive({img: "", // 裁剪图片的地址outputSize: 1, // 裁剪生成图片的质量(可选0.1 - 1)outputType: "jpeg", // 裁剪生成图片的格式(jpeg || png || webp)info: false, // 图片大小信息canScale: true, // 图片是否允许滚轮缩放autoCrop: true, // 是否默认生成截图框autoCropWidth: 230, //默认生成截图框宽度autoCropHeight: 150, //默认生成截图框高度fixed: false, // 是否开启截图框宽高固定比例fixedNumber: [1.53, 1], //截图框的宽高比例full: false, // false按原比例裁切图片,不失真fixedBox: false, // 固定截图框大小,不允许改变canMove: true, // 上传图片是否可以移动canMoveBox: true, // 截图框能否拖动original: true, // 上传图片按照原始比例渲染centerBox: true, // 截图框是否被限制在图片里面high: false, // 是否按照设备的dpr 输出等比例图片infoTrue: false, // true为展示真实输出图片宽高,false展示看到的截图框宽高maxImgSize: 3000, // 限制图片最大宽度和高度enlarge: 1, // 图片根据截图框输出比例倍数mode: "550px 400px", // 图片默认渲染方式limitMinSize: [108, 108], // 裁剪框限制最小区域minCropBoxWidth: 108, // 设置最小裁切框宽度minCropBoxHeight: 108, // 设置最小裁切框高度
});// 类型,大小判断
const judegFileSize = (file) => {const filterSize = (size) => {const pow1024 = (num) => {return Math.pow(1024, num);};if (!size) return "";if (size < pow1024(1)) return size + " B";if (size < pow1024(2)) return (size / pow1024(1)).toFixed(0) + " KB";if (size < pow1024(3)) return (size / pow1024(2)).toFixed(0) + " MB";if (size < pow1024(4)) return (size / pow1024(3)).toFixed(0) + " GB";return (size / pow1024(4)).toFixed(2) + " TB";};let retunBoolean = true;let fileSize = file.size;//判断文件类型const fileExtArray = file.name.split(".");const judegFn = () => {if (acceptArray.indexOf(fileExtArray.at(-1)) == -1) {alert(`${file.name}上传失败,只能上传${acceptArray.join("、")}`);retunBoolean = false;}};if (acceptArray.length > 0) {if (acceptArray.indexOf("image") != -1) {var pattern = /(\.jpg|\.jpeg|\.png|\.gif)$/i;// 判断文件名是否匹配图片格式的正则表达式if (!pattern.test(`.${fileExtArray.at(-1)}`)) {judegFn();}} else {judegFn();}}if (retunBoolean) {if (props.size > 0 && fileSize > props.size) {alert(`最大上传${filterSize(props.size)}`);retunBoolean = false;}}return retunBoolean;
};
const beforeAvatarUpload = (rawFile) => {let retunBoolean = judegFileSize(rawFile);if (retunBoolean) {fileList.push({src: URL.createObjectURL(rawFile),file: rawFile,});}return retunBoolean;
};// 裁剪
const cropperFn = (item, index) => {showCropper.value = true;option.img = URL.createObjectURL(item.file);const reader = new FileReader();reader.readAsDataURL(item.file);cropperCb.value = (res) => {if (res) {cropperRefs.value.getCropBlob((data) => {const result = new File([data], item.file.name, {type: item.file.type,lastModified: Date.now(),});result["uid"] = item.file.uid;fileList.splice(index, 1, {src: URL.createObjectURL(result),file: result,});showCropper.value = false;});}};
};// 删除
const delFn = (index) => {fileList.splice(index, 1);
};
// 弹窗确定裁剪
const onSubmit = () => {if (cropperCb.value) cropperCb.value(true);
};// 真实上传
const uploadFileFn = (item) => {if (props.sendUrl == "") return false;const successFn = (url) => {const index = fileList.findIndex((n) => {if (n.file && n.file.uid == item.file.uid) {return true;}return false;});if (index != -1) {fileList.splice(index, 1, {src: url,file: item.file,isSuccess: true,});}};// successFn(item.src);const formData = new FormData();formData.append("file", item.file);if (props.otherData) {Object.keys(props.otherData).forEach((key) => {formData.append(key, props.otherData[key]);});}fetch(props.sendUrl, {method: "POST",body: formData,headers: props.headers,"Content-type": "multipart/form-data",}).then((respone) => respone.json()).then((res) => {// 接口成功后替换urlsuccessFn("成功的url");}).catch((error) => {// 接口失败的});
};
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;border: 1px solid #ccc;width: 178px;height: 178px;text-align: center;
}.uploadMian {vertical-align: top;display: flex;flex-wrap: wrap;
}
.avatar-uploader {
}.img-item {display: inline-block;width: 178px;height: 178px;margin-right: 10px;border: 1px solid #ccc;position: relative;img {width: 100%;height: 100%;object-fit: contain;position: relative;z-index: 9;}&:hover{.el-icon.uploader-close {display: flex !important;}}.uploader-Check{width: 40px;height: 40px;position: absolute;z-index: 18;top: 0;left: 0;display: flex;background-color: #67c23a;clip-path: polygon(0 0 ,100% 0, 0 100%);-webkit-clip-path:polygon(0 0 ,100% 0,0 100% );.el-icon{position: absolute;top: 4px;left: 4px;color: #fff;}}.el-icon.uploader-close {display: none;position: absolute;z-index: 20;top: -5px;right: -5px;width: 20px;height: 20px;background-color: red;justify-content: center;align-items: center;border-radius: 50%;color: #fff;font-size: 12px;cursor: pointer;}.button-div {position: absolute;height: 45px;z-index: 20;bottom: 0;left: 0;width: 100%;background-color: rgba(0, 0, 0, 0.2);display: flex;justify-content: space-around;align-items: center;}
}
.cropper-content {display: flex;display: -webkit-flex;justify-content: flex-end;.cropper-box {width: 550px;.cropper {width: auto;height: 400px;}}.show-preview {flex: 1;-webkit-flex: 1;display: flex;display: -webkit-flex;justify-content: center;.preview {overflow: hidden;border: 1px solid #67c23a;background: #cccccc;}}
}
.dialog-footer {display: flex;justify-content: center;margin-top: 10px;
}
</style>

5.使用:

 <cropperUpload :otherData="{a:100}" :headers="{}" v-model="urlList" :multiple="true" sendUrl="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" /><script setup>import cropperUpload from "./cropperUpload.vue";// 裁剪的配置const urlList = reactive(['https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500'])</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/236211.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pycharm中使用matplotlib绘制动态图形

Pycharm中使用matplotlib绘制动态图形 最终效果 最近用pycharm学习D2L时发现官方在jupyter notebook交互式环境中能动态绘制图形&#xff0c;但是在pycharm脚本环境中只会在最终 plt.show() 后输出一张静态图像。于是有了下面这段自己折腾了一下午的代码&#xff0c;用来在pych…

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动 链接&#xff1a;https://pan.baidu.com/s/16lpEW6L5jrHfhsG7EXocLw?pwdkryy 提取码&#xff1a;kryy <!--社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 s…

Vue的Nuxt项目部署在服务器,pm2动态部署和npm run build静态部署

Nuxt项目的部署有两种方式&#xff0c;一种是静态部署&#xff0c;一种是动态部署 静态部署需要关闭项目的ssr功能&#xff0c;动态部署则不需关闭&#xff0c;所以怎么部署项目就看你用不用ssr功能了 。 1.静态部署 先说静态部署&#xff0c;很简单&#xff0c;只需要在nuxt…

蓝桥杯每日一题2023.11.30

题目描述 九数组分数 - 蓝桥云课 (lanqiao.cn) 题目分析 此题目实际上是使用dfs进行数字确定&#xff0c;每次循环中将当前数字与剩下的数字进行交换 eg.1与2、3、4、、、进行交换 2与3、4、、、进行交换 填空位置将其恢复原来位置即可&#xff0c;也就直接将其交换回去即可…

OpenCV中八种不同的目标追踪算法

引言 目标跟踪作为机器学习的一个重要分支&#xff0c;加之其在日常生活、军事行动中的广泛应用&#xff0c;受到极大的关注。在AI潮流中&#xff0c;大家对于深度学习&#xff0c;目标跟踪肯定都会有过接触了解&#xff1a;在GPU上通过大量的数据集训练出自己想使用的垂直场景…

Open3D 最小二乘拟合二维直线(直接求解法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重。 一、算法原理 平面直线的表达式为: y = k x + b

如何配置mybatis中mapper对应关系,解决mybatis报错:Invalid bound statement (not found):

先看一下报错信息&#xff1a; Invalid bound statement&#xff1a;意思是无效的绑定语句 原因就是&#xff1a;在使用mybatis时mapper.xml没有和mapper接口对应起来 解决方式 第一种&#xff1a; 将mapper.xml和mapper接口放在同一位置 在pom中配置&#xff1a; <reso…

计网Lesson5 - MAC 地址与 ARP

文章目录 M A C MAC MAC 地址1. M A C MAC MAC 地址的格式 2. M A C MAC MAC 地址的获取3. A R P ARP ARP 协议4. A R P ARP ARP 缓存5. R A R P RARP RARP M A C MAC MAC 地址 1. M A C MAC MAC 地址的格式 每个网卡都有一个 6 6 6 字节的 M A C MAC MAC 地址 M A C…

分支和循环

通常来说&#xff0c;C语言是结构化的程序设计语言&#xff0c;这里的结构包括顺序结构、选择结构、循环结构&#xff0c;C语言能够实现这三种结构&#xff0c;如果我们仔细分析&#xff0c;我们日常生活中所见的事情都可以拆分为这三种结构或者它们的组合。 下面我会仔细讲解我…

计算机软件的分类

以功能进行分类&#xff0c;计算机软件通常可以分为系统软件和应用软件两大类。 系统软件&#xff1a;系统软件是计算机运行和管理的基本软件&#xff0c;包括操作系统、驱动程序、系统工具和服务程序等。操作系统是系统软件的核心&#xff0c;负责管理计算机的硬件资源、提供用…

数字图像处理(实践篇)十四 图像金字塔

目录 一 图像金字塔 二 涉及的函数 三 实践 一 图像金字塔 在某些情况下&#xff0c;需要处理不同分辨率的&#xff08;相同&#xff09;图像。比如&#xff0c;在图像中搜索某些目标&#xff08;比如人脸&#xff09;的时候&#xff0c;不确定该目标在所述图像中会以多大的…

java+springboot实验室管理系统的设计与实现ssm+jsp

课题研究内容&#xff1a; &#xff08;1&#xff09; 系统需求分析&#xff08;构成模块&#xff0c;系统流程&#xff0c;功能结构图&#xff0c;系统需求&#xff09; &#xff08;2&#xff09; 实验室课程安排功能模块&#xff08;课程的录入和调补&#xff09; &#xff…