图片文件剪裁-上传(Vue3)
要求
- 上传图片文件
- 上传图片时,需要剪裁到要求尺寸
- 预览图片
实现
1. 上传样式(index.vue)
-
默认显示一张图片(avatar.png),上传后显示返回的在线图片链接,重新上传时更新。
-
UploadImg为封装的组件,带有插槽(触发上传)
```html <!-- import UploadImg from "@/components/UploadImg.vue"; --> <div class="m-avatar"> <img v-if="myAvatar" :src="myAvatar" onerror="this.src='./avatar.png'; this.οnerrοr=null;" /> <img v-else src="@/assets/img/avatar.png" /> <UploadImg @success="uploadSuccess"> 上传头像 </UploadImg> </div> ``` ```ts /* 图片上传 */ const myAvatar = ref<string>(""); const uploadSuccess = (imgurl: string) => { myAvatar.value = imgurl; }; ```
2. UploadImg上传组件(UploadImg.vue)
-
Element-plus组件
```html <div class="upload"> <div class="m-upload"> <el-upload v-model:file-list="avatarImg" :http-request="customUpload" :limit="1" :show-file-list="false" action="action" :before-upload="beforeAvatarUpload" > <div class="u-title"><slot> </slot></div> <!-- 插槽 --> </el-upload> </div> </div> ```
3. 图片剪裁(UploadImg.vue)
-
vue-cropper
```js // 安装npm install vue-cropper@next // 配置 main.js import VueCropper from 'vue-cropper'; import 'vue-cropper/dist/index.css' app.use(VueCropper) ``` -
剪裁窗口:当图片不满足上传要求时,需要剪裁后上传,否则不剪裁直接上传。
```html <el-dialog v-model="cropperVisible" title="图片剪裁" width="400" :before-close="beforeClose" draggable > <div class="imgCropper"> <!-- 剪裁框 --> <div class="cropper"> <vue-cropper ref="cropperRef" :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" :full="option.full" :fixedBox="option.fixedBox" :canMove="option.canMove" :canMoveBox="option.canMoveBox" :original="option.original" :centerBox="option.centerBox" :height="option.height" :infoTrue="option.infoTrue" :enlarge="option.enlarge" > </vue-cropper> </div> <!--底部操作工具按钮--> <div class="footer-btn"> <div class="scope-btn"> <!-- 放大 --> <el-button type="primary" plain icon="zoom-in" @click="changeScale(1)" ></el-button> <!-- 缩小 --> <el-button type="primary" plain icon="zoom-out" @click="changeScale(-1)" ></el-button> <!-- 此处仅用到放大缩小,还有旋转,参考官方函数 --> </div> <div class="upload-btn"> <el-button type="success" @click="uploadImg" >确认保存<i class="upload"></i ></el-button> </div> </div> </div> </el-dialog> ``` -
判断是否需要剪裁 (以及格式)
```ts // 文件上传前检查 const beforeAvatarUpload: UploadProps["beforeUpload"] = async ( rawFile: any ) => { try { // 图片格式检查 if (rawFile.type !== "image/jpeg" && rawFile.type !== "image/png") { ElMessage.error("头像图片必须是JPG/PNG格式!"); return false; } // 由于onload读取文件异步,将其封装成Promise对象,await等待异步结果 let ret: boolean = true; // 函数里没法直接返回到外层 await readFile(rawFile) .then((res: any) => { ret = res; }) .catch((err) => { console.log(err); }); return ret; } catch (error) { console.log(error); } }; // 判断是否需要剪裁 const readFile = (file: any) => { // 返回Promise对象 return new Promise(function (resolve, reject) { let reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function (theFile: any) { // theFile看起来是读取事件对象 const image: any = new Image(); image.src = theFile.target.result; image.onload = function (res: any) { // 两层onload,不然加载不到图片 if (res.target.width / res.target.height !== 70 / 98) { // 不符合尺寸要求,进行剪裁 cropperVisible.value = true; // 打开剪裁窗口 // 设置cropper剪裁的图片(cropper配置见下文) if (typeof image.src === "object") { option.img = window.URL.createObjectURL(new Blob([image.src])); } else { option.img = image.src; } resolve(false); } else { resolve(true); } }; }; }); }; ``` -
剪裁框配置
```js /* cropper 调整了部分,完整具体参考官方 */ const cropperRef = ref(); const option = reactive({ img: "", //裁剪图片的地址 outputSize: 1, //裁剪生成图片的质量(可选0.1 - 1) outputType: "jpeg", //裁剪生成图片的格式(jpeg || png || webp) info: true, //图片大小信息 canScale: true, //图片是否允许滚轮缩放 autoCrop: true, //是否默认生成截图框 autoCropWidth: 300, //默认生成截图框宽度 autoCropHeight: 420, //默认生成截图框高度 fixed: false, //是否开启截图框宽高固定比例 // fixedNumber: [380, 420], //截图框的宽高比例 full: false, //false按原比例裁切图片,不失真 fixedBox: true, //固定截图框大小,不允许改变 canMove: true, //上传图片是否可以移动 canMoveBox: false, //截图框能否拖动 original: false, //上传图片按照原始比例渲染 centerBox: false, //截图框是否被限制在图片里面 height: true, //是否按照设备的dpr 输出等比例图片 infoTrue: false, //true为展示真实输出图片宽高,false展示看到的截图框宽高 enlarge: 1, //图片根据截图框输出比例倍数 }); //图片缩放 const changeScale = (num: number) => { num = num || 1; cropperRef.value.changeScale(num); }; ```
4. 上传文件
-
上传时机
- 尺寸正确时beforeAvatarUpload返回true自动调用customUpload
- 剪裁后,确认保存按钮调用uploadImg
const customUpload = (file: any) => { uploadFile(file.file); }; const uploadImg = () => { cropperVisible.value = false cropperRef.value.getCropBlob(async (data: any) => { uploadFile(data); }); }; -
axios上传到服务器图片文件夹,返回在线图片文件名。调用emits,将文件名传递给父组件。
```ts // import { getToken } from "@/utils/storage"; // import axios from "axios"; const uploadFile = (filedata: any) => { let formData = new FormData(); formData.append("file", filedata); // 使用FormData传输 const token = getToken("Token"); axios({ url: "/common/upload", method: "post", headers: { "content-type": "multipart/form-data", // 数据类型 Authorization: token, // token }, data: formData, }) .then((response) => { if (response.data.code && response.data.code == 200) { // 上传成功 emits("success", response.data.fileName); // 封装为上传组件,将返回的文件名传给父组件 } else { ElMessage.error(response.data.msg); } }) .catch(() => { ElMessage.error("上传图片失败"); }); avatarImg.value = []; // file-list 限制一张,每次上传完置空 const avatarImg = ref<any[]>([]); }; ```
5. 其他
-
使用了简单的ts,js编写时需要把类型去掉
-
css不再说明,按需编写
-
仅对文件上传、剪裁逻辑梳理