图片文件剪裁-上传(Vue3)

494 阅读4分钟

图片文件剪裁-上传(Vue3)


官方vue-cropper - GitCode


要求

  1. 上传图片文件
  2. 上传图片时,需要剪裁到要求尺寸
  3. 预览图片

实现

1. 上传样式(index.vue)

  1. 默认显示一张图片(avatar.png),上传后显示返回的在线图片链接,重新上传时更新。 readme.jpg

  2. 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)

  1. 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)

  1. vue-cropper

     ```js
     // 安装npm install vue-cropper@next
    
     // 配置 main.js
     import VueCropper from 'vue-cropper'; 
     import 'vue-cropper/dist/index.css'
     app.use(VueCropper)
     ```
    
  2. 剪裁窗口:当图片不满足上传要求时,需要剪裁后上传,否则不剪裁直接上传。

     ```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>
     ```
    
  3. 判断是否需要剪裁 (以及格式)

     ```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);
     				}
     			};
     		};
     	});
     };
    
     ```
    
  4. 剪裁框配置

     ```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. 上传文件

  1. 上传时机

    • 尺寸正确时beforeAvatarUpload返回true自动调用customUpload
    • 剪裁后,确认保存按钮调用uploadImg
    
    const customUpload = (file: any) => {
    	uploadFile(file.file);
    };
    const uploadImg = () => {
    	cropperVisible.value = false
    	cropperRef.value.getCropBlob(async (data: any) => {
    		uploadFile(data);
    	});
    };
    
  2. 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. 其他

  1. 使用了简单的ts,js编写时需要把类型去掉

  2. css不再说明,按需编写

  3. 仅对文件上传、剪裁逻辑梳理