Files
vue3/packages/upload-avatar/index.vue
2023-07-20 17:16:56 +08:00

221 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="upload-avatar">
<div class="el-upload el-upload--picture-card" @click="showDialog = !disabled">
<el-avatar v-if="avatarUrl" :src="avatarUrl" :size="146" alt="头像" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</div>
<slot name="text">
<div v-show="!disabled" class="el-upload__tip w-400px">
<slot name="tip">
单张图片不大于{{ fileSize }}MB且格式为jpegjpgpng或bmp只可上传1张
</slot>
</div>
</slot>
</div>
<el-dialog v-model="showDialog" title="上传头像" width="800px" append-to-body @opened="modalOpened" @closed="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<VueCropper
v-if="visible" ref="cropperRef" :img="cropperURL" :info="true" :auto-crop="options.autoCrop"
:auto-crop-width="autoCropWidth" :auto-crop-height="autoCropHeight" :fixed-box="options.fixedBox"
:output-type="options.outputType" @real-time="realTime"
/>
</el-col>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<div
absolute h-200px w-200px rd="1/2" top="1/2" overflow-hidden
class="translate-x-1/2 -translate-y-1/2" :style="imageStyle"
>
<img v-show="options.previews.url" :src="options.previews.url" :style="options.previews.img" alt="实时预览图片">
</div>
</el-col>
</el-row>
<br>
<el-row>
<el-col :lg="2" :md="2">
<el-button @click="selectImage">
选择
<el-icon class="el-icon--right">
<Upload />
</el-icon>
</el-button>
</el-col>
<el-col :lg="{ span: 1, offset: 2 }" :md="2">
<el-button :icon="Plus" @click="changeScale(1)" />
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="Minus" @click="changeScale(-1)" />
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="RefreshLeft" @click="rotateLeft()" />
</el-col>
<el-col :lg="{ span: 1, offset: 1 }" :md="2">
<el-button :icon="RefreshRight" @click="rotateRight()" />
</el-col>
<el-col :lg="{ span: 2, offset: 6 }" :md="2">
<el-button type="primary" :loading="loading" @click="confirmUpload">
</el-button>
</el-col>
</el-row>
</el-dialog>
</template>
<script lang="ts" setup name="ThUploadAvatar">
import { ElMessage } from 'element-plus'
import type { CSSProperties } from 'vue'
import { VueCropper } from 'vue-cropper'
import { Minus, Plus, RefreshLeft, RefreshRight, Upload } from '@element-plus/icons-vue'
const props = withDefaults(defineProps<{
accpet?: string
modelValue?: string
/**
* 图片大小单位MB
*
* @default 2
*/
fileSize?: number
/** 默认生成截图框宽度 */
autoCropHeight?: number
/** 默认生成截图框高度 */
autoCropWidth?: number
uploadFunction?: (formData: FormData) => Promise<FileVO>
disabled?: boolean
}>(), {
accpet: '.jpeg,.jpg,.png,.bmp',
fileSize: 2,
autoCropHeight: 200,
autoCropWidth: 200,
uploadFunction: () => Promise.resolve({}),
})
const emit = defineEmits<{
(event: 'update:modelValue', modelValue?: string): void
(event: 'onSuccess', file: FileVO): void
(event: 'onError', error: Error): void
}>()
const avatarUrl = useVModel(props, 'modelValue', emit)
const imageStyle = computed(() => ({
height: `${props.autoCropHeight}px`,
width: `${props.autoCropWidth}px`,
boxShadow: '0 0 4px #ccc',
}))
let loading = $ref(false)
interface PreviewData {
div: CSSProperties
h: number
html: string
img: CSSProperties
url: string
w: number
}
interface Option {
/** 裁剪图片的地址 */
img: any
/** 是否默认生成截图框 */
autoCrop: boolean
/** 固定截图框大小 不允许改变 */
fixedBox: boolean
/** 默认生成截图为PNG格式 */
outputType: string
/** 预览数据 */
previews: Partial<PreviewData>
visible: boolean
}
// 图片裁剪数据
const options = reactive<Option>({
img: '',
autoCrop: true,
fixedBox: true,
outputType: 'png',
previews: {},
visible: false,
})
const visible = ref(false)
const cropperRef = $ref<InstanceType<typeof VueCropper>>()
const { open, reset, onChange } = useFileDialog()
let showDialog = $ref(false)
/** 向左旋转 */
function rotateLeft() {
cropperRef?.rotateLeft()
}
/** 向右旋转 */
function rotateRight() {
cropperRef?.rotateRight()
}
/** 打开弹出层结束时的回调 */
function modalOpened() {
visible.value = true
}
/** 图片缩放 */
function changeScale(num?: number) {
cropperRef?.changeScale(num || 1)
}
/** 实时预览 */
function realTime(data: PreviewData) {
options.previews = data
}
const file = shallowRef()
const cropperURL = useObjectUrl(file)
onChange((param) => {
file.value = param?.item(0)
})
function selectImage() {
open({
accept: props.accpet,
multiple: false,
})
}
async function uploadImg(blob: Blob) {
if (blob.size > props.fileSize * 1024 * 1024) {
ElMessage.error('您的图片经过剪裁依然很大,请重新选择图片')
return
}
loading = true
const formData = new FormData()
formData.append('file', blob)
try {
const res = await props.uploadFunction(formData)
avatarUrl.value = res.url
showDialog = false
emit('onSuccess', res)
}
catch (err: any) {
emit('onError', err)
loading = false
}
}
function confirmUpload() {
cropperRef?.getCropBlob(uploadImg)
}
/** 关闭窗口 */
function closeDialog() {
options.visible = false
reset()
loading = false
}
</script>
<style>
@import url('vue-cropper/dist/index.css');
@import url('element-plus/theme-chalk/el-upload.css');
</style>