2021年10月28日 vue
头像上传剪切组件的封装
因为antd vue2.2.0并没有剪切图片的功能所以自己封装
<template>
<div ref="cropper-upload" class="cropper-upload">
<a-upload
accept="image/*"
list-type="picture-card"
class="avatar_uploader"
:show-upload-list="false"
:before-upload="beforeUpload"
:disabled="disabled"
>
<!-- 可以编辑 -->
<div v-if="!disabled">
<!-- 没的头像可以上传 -->
<div v-if="!imageUrl2">
<upload-outlined :style="{ fontSize: '16px' }" />
<div class="ant-upload-text">上传头像</div>
</div>
<!-- 展示 -->
<img
v-else
class="avatar_uploader"
:src="`/api/media/api/v1/media/showImage/${imageUrl2 ?? 'avatar'}`"
/>
</div>
<!-- 当不能编辑时 -->
<div v-else>
<div v-if="!imageUrl2"></div>
<img
v-else
class="avatar_uploader"
:src="`/api/media/api/v1/media/showImage/${imageUrl2 ?? 'avatar'}`"
/>
</div>
</a-upload>
<a-modal
v-model:visible="visible"
title="编辑图片"
width="520px"
wrap-class-name="cropper_upload_madal"
:centered="true"
:destroy-on-close="true"
:mask-closable="false"
@ok="handelOk"
>
<img ref="imageRef" :src="imageUrl" class="before-cropper" />
<img class="before" />
</a-modal>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, nextTick, ref, watch } from 'vue';
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
import { message } from 'ant-design-vue';
import { UploadImageAction } from '@/store/modules/StudentManageStore/actions';
import { useStore } from 'vuex';
import { UploadOutlined } from '@ant-design/icons-vue';
interface FileItem {
uid: string;
name?: string;
status?: string;
response?: string;
url?: string;
type?: string;
size: number;
originFileObj: Blob;
preview?: string;
file: string | Blob;
lastModified: number;
arrayBuffer: Buffer;
slice: string;
stream: Blob;
text: string;
}
export default defineComponent({
name: 'CropperUpliad',
components: {
UploadOutlined,
},
props: {
// 默认值
portraitId: { type: String, required: true },
// 是否禁用
disabled: { type: Boolean },
},
emits: ['getId'],
setup(props, content) {
const store = useStore();
// 保存剪裁对象
let myCropper = ref<null | Cropper>(null);
// imageDom对象
const imageRef = ref<HTMLImageElement>();
// 剪切之后的数据---指的是上传之后接口返回的文件id
const portraitId = computed(() => {
console.log(props.portraitId);
return props.portraitId as string;
});
const imageUrl2 = ref<string | null>(null);
watch(
() => portraitId.value,
() => {
imageUrl2.value = portraitId.value;
},
);
// 源数据
const imageUrl = ref<string>('');
// 弹窗是否可见
const visible = ref<boolean>(false);
//剪切之后的数据
const afterImage = ref<Blob | null>(null);
// 文件名称
const fileName = ref<string | null | undefined>(null);
// copper初始化函数
const init = () => {
if (!visible.value) return;
if (!imageRef.value) return;
myCropper.value = new Cropper(imageRef.value, {
viewMode: 1,
dragMode: 'none',
initialAspectRatio: 1,
aspectRatio: 1,
preview: '.before',
background: false,
autoCropArea: 0.6,
zoomOnWheel: false,
});
};
// File转Base64
const transformFile = (file) => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
resolve(reader.result as string);
};
});
};
const beforeUpload = async (file: FileItem) => {
const isJpgOrPng =
file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('你只能上传JPG/PNG文件!');
}
const isLt2M = file.size / 1024 / 1024 < 10;
if (!isLt2M) {
message.error('图片大小要小于10MB!');
}
if (isJpgOrPng && isLt2M) {
// 转base64
const result = await transformFile(file);
imageUrl.value = result as string;
fileName.value = file.name;
visible.value = true;
// 初始化
nextTick(() => {
init();
});
}
// 手动上传
return new Promise((resolve, reject) => {
reject();
});
};
// 点击上传
const handelOk = () => {
// 有些浏览器不支持Canvas.toBlob
if (!myCropper.value) return;
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
len = binStr.length,
arr = new Uint8Array(len);
for (var i = 0; i < len; i++) {
arr[i] = binStr.charCodeAt(i);
}
callback(new Blob([arr], { type: type || 'image/png' }));
},
});
}
myCropper.value
.getCroppedCanvas({
imageSmoothingQuality: 'high',
})
.toBlob(
function (blob) {
store
.dispatch(UploadImageAction, {
file: blob,
fileName: fileName.value,
})
.then(() => {
if (store.state.StudentManageStore.result) {
visible.value = false;
content.emit(
'getId',
store.state.StudentManageStore.addMediaRes,
);
imageUrl2.value = store.state.StudentManageStore.addMediaRes;
}
});
},
'image/jpeg',
1,
);
};
return {
myCropper,
beforeUpload,
visible,
imageRef,
transformFile,
imageUrl,
imageUrl2,
afterImage,
store,
fileName,
handelOk,
};
},
});
</script>
<style lang="less">
.before-cropper {
width: 472px;
height: 472px;
object-fit: cover;
// size: 472px;
}
.cropper_upload_madal {
.ant-modal-body {
padding: 10px 0;
.cropper-modal {
padding: 0 20px;
}
}
}
</style>
a-upload 时antd的上传组件
参数解释:接受图片类型、以图片形式展示上传的文件、但是不展示文件列表、上传之前做的操作、是否禁用;
中间部分是禁用时的展示以及上传之后显示最新的图片;
最下面弹窗是通过beforeUpload调起的;
props:portraitid是头像,disabled是是否禁用
beforeUpload函数首先校验是否是图片、然后校验大小、最后把图片转换为base64、nextTick下一帧则调用Cropper将图片文件imageRef传入、return Promise是为了禁止antd组件的自动上传、点击上传先看浏览器有无Canvas.toBlob没有就走封装的方法、有的话就调用getCroppedCanvas().toBlob转blob完成上传
最后通过事件将后端返回的文件id抛出组件外