admin-vben/apps/web-ele/src/components/cropper/cropper-modal.vue

333 lines
9.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts" setup>
import type { CropendResult, CropperModalProps, CropperType } from './typing';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { dataURLtoBlob, isFunction } from '@vben/utils';
import {
ElAvatar,
ElButton,
ElMessage,
ElSpace,
ElTooltip,
ElUpload,
} from 'element-plus';
import CropperImage from './cropper.vue';
defineOptions({ name: 'CropperModal' });
const props = withDefaults(defineProps<CropperModalProps>(), {
circled: true,
size: 0,
src: '',
uploadApi: () => Promise.resolve(),
});
const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']);
let filename = '';
const src = ref(props.src || '');
const previewSource = ref('');
const cropper = ref<CropperType>();
let scaleX = 1;
let scaleY = 1;
const [Modal, modalApi] = useVbenModal({
onConfirm: handleOk,
onOpenChange(isOpen) {
if (isOpen) {
// 打开时,进行 loading 加载。后续 CropperImage 组件加载完毕,会自动关闭 loading通过 handleReady
modalLoading(true);
} else {
// 关闭时,清空右侧预览
previewSource.value = '';
modalLoading(false);
}
},
});
function modalLoading(loading: boolean) {
modalApi.setState({ confirmLoading: loading, loading });
}
// Block upload
function handleBeforeUpload(file: File) {
if (props.size > 0 && file.size > 1024 * 1024 * props.size) {
emit('uploadError', { msg: $t('ui.cropper.imageTooBig') });
return false;
}
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
reader.addEventListener('load', (e) => {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
});
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: CropperType) {
cropper.value = cropperInstance;
// 画布加载完毕 关闭 loading
modalLoading(false);
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
(cropper?.value as any)?.[event]?.(arg);
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
if (!previewSource.value) {
ElMessage.warning('未选择图片');
return;
}
const blob = dataURLtoBlob(previewSource.value);
try {
modalLoading(true);
const url = await uploadApi({ file: blob, filename, name: 'file' });
emit('uploadSuccess', { data: url, source: previewSource.value });
await modalApi.close();
} finally {
modalLoading(false);
}
}
}
</script>
<template>
<Modal
v-bind="$attrs"
:confirm-text="$t('ui.cropper.okText')"
:fullscreen-button="false"
:title="$t('ui.cropper.modalTitle')"
class="w-[800px]"
>
<div class="flex">
<!-- 左侧区域 -->
<div class="h-[340px] w-[55%]">
<!-- 裁剪器容器 -->
<div
class="h-[300px] bg-[#eee]"
style="
background-image:
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
),
linear-gradient(
45deg,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgb(0 0 0 / 25%) 0
);
background-position:
0 0,
12px 12px;
background-size: 24px 24px;
"
>
<CropperImage
v-if="src"
:circled="circled"
:src="src"
height="300px"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>
<!-- 工具栏 -->
<div class="mt-2.5 flex items-center justify-between">
<ElUpload
:before-upload="handleBeforeUpload"
:file-list="[]"
accept="image/*"
>
<ElTooltip
:content="$t('ui.cropper.selectImage')"
placement="bottom"
>
<ElButton size="small" type="primary">
<template #icon>
<div class="flex items-center justify-center">
<span class="icon-[ant-design--upload-outlined]"></span>
</div>
</template>
</ElButton>
</ElTooltip>
</ElUpload>
<ElSpace>
<ElTooltip :content="$t('ui.cropper.btn_reset')" placement="bottom">
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('reset')"
>
<template #icon>
<div class="flex items-center justify-center">
<span class="icon-[ant-design--reload-outlined]"></span>
</div>
</template>
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ui.cropper.btn_rotate_left')"
placement="bottom"
>
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('rotate', -45)"
>
<template #icon>
<div class="flex items-center justify-center">
<span
class="icon-[ant-design--rotate-left-outlined]"
></span>
</div>
</template>
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ui.cropper.btn_rotate_right')"
placement="bottom"
>
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('rotate', 45)"
>
<template #icon>
<div class="flex items-center justify-center">
<span
class="icon-[ant-design--rotate-right-outlined]"
></span>
</div>
</template>
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ui.cropper.btn_scale_x')"
placement="bottom"
>
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('scaleX')"
>
<template #icon>
<div class="flex items-center justify-center">
<span class="icon-[vaadin--arrows-long-h]"></span>
</div>
</template>
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ui.cropper.btn_scale_y')"
placement="bottom"
>
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('scaleY')"
>
<template #icon>
<div class="flex items-center justify-center">
<span class="icon-[vaadin--arrows-long-v]"></span>
</div>
</template>
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ui.cropper.btn_zoom_in')"
placement="bottom"
>
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('zoom', 0.1)"
>
<template #icon>
<div class="flex items-center justify-center">
<span class="icon-[ant-design--zoom-in-outlined]"></span>
</div>
</template>
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ui.cropper.btn_zoom_out')"
placement="bottom"
>
<ElButton
:disabled="!src"
size="small"
type="primary"
@click="handlerToolbar('zoom', -0.1)"
>
<template #icon>
<div class="flex items-center justify-center">
<span class="icon-[ant-design--zoom-out-outlined]"></span>
</div>
</template>
</ElButton>
</ElTooltip>
</ElSpace>
</div>
</div>
<!-- 右侧区域 -->
<div class="h-[340px] w-[45%]">
<!-- 预览区域 -->
<div
class="mx-auto h-[220px] w-[220px] overflow-hidden rounded-full border border-gray-200"
>
<img
v-if="previewSource"
:alt="$t('ui.cropper.preview')"
:src="previewSource"
class="h-full w-full"
/>
</div>
<!-- 头像组合预览 -->
<template v-if="previewSource">
<div
class="mt-2 flex items-center justify-around border-t border-gray-200 pt-2"
>
<ElAvatar :src="previewSource" size="large" />
<ElAvatar :size="48" :src="previewSource" />
<ElAvatar :size="64" :src="previewSource" />
<ElAvatar :size="80" :src="previewSource" />
</div>
</template>
</div>
</div>
</Modal>
</template>