123 lines
3.2 KiB
Vue
123 lines
3.2 KiB
Vue
<script lang="ts" setup>
|
||
import type { CSSProperties } from 'vue';
|
||
|
||
import type { CropperAvatarProps } from './typing';
|
||
|
||
import { computed, ref, unref, watch, watchEffect } from 'vue';
|
||
|
||
import { useVbenModal } from '@vben/common-ui';
|
||
import { $t } from '@vben/locales';
|
||
|
||
import { ElButton, ElMessage } from 'element-plus';
|
||
|
||
import cropperModal from './cropper-modal.vue';
|
||
|
||
defineOptions({ name: 'CropperAvatar' });
|
||
|
||
const props = withDefaults(defineProps<CropperAvatarProps>(), {
|
||
width: 200,
|
||
value: '',
|
||
showBtn: true,
|
||
btnProps: () => ({}) as any,
|
||
btnText: '',
|
||
uploadApi: () => Promise.resolve(),
|
||
size: 5,
|
||
});
|
||
|
||
const emit = defineEmits(['update:value', 'change']);
|
||
|
||
const sourceValue = ref(props.value || '');
|
||
const [CropperModal, modalApi] = useVbenModal({
|
||
connectedComponent: cropperModal,
|
||
});
|
||
|
||
const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`);
|
||
|
||
const getIconWidth = computed(
|
||
() => `${Number.parseInt(`${props.width}`.replace(/px/, '')) / 2}px`,
|
||
);
|
||
|
||
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
|
||
|
||
const getImageWrapperStyle = computed(
|
||
(): CSSProperties => ({ height: unref(getWidth), width: unref(getWidth) }),
|
||
);
|
||
|
||
watchEffect(() => {
|
||
sourceValue.value = props.value || '';
|
||
});
|
||
|
||
watch(
|
||
() => sourceValue.value,
|
||
(v: string) => {
|
||
emit('update:value', v);
|
||
},
|
||
);
|
||
|
||
function handleUploadSuccess({ data, source }: any) {
|
||
sourceValue.value = source;
|
||
emit('change', { data, source });
|
||
ElMessage.success($t('ui.cropper.uploadSuccess'));
|
||
}
|
||
|
||
const closeModal = () => modalApi.close();
|
||
const openModal = () => modalApi.open();
|
||
|
||
defineExpose({
|
||
closeModal,
|
||
openModal,
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<!-- 头像容器 -->
|
||
<div class="inline-block text-center" :style="getStyle">
|
||
<!-- 图片包装器 -->
|
||
<div
|
||
class="group relative cursor-pointer overflow-hidden rounded-full border border-gray-200 bg-white"
|
||
:style="getImageWrapperStyle"
|
||
@click="openModal"
|
||
>
|
||
<!-- 遮罩层 -->
|
||
<div
|
||
class="duration-400 absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-black bg-opacity-40 opacity-0 transition-opacity group-hover:opacity-100"
|
||
:style="getImageWrapperStyle"
|
||
>
|
||
<!-- TODO @puhui999:可以改成类似 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/components/cropper/cropper-avatar.vue 里的 Icon 么? -->
|
||
<span
|
||
:style="{
|
||
...getImageWrapperStyle,
|
||
width: getIconWidth,
|
||
height: getIconWidth,
|
||
lineHeight: getIconWidth,
|
||
}"
|
||
class="icon-[ant-design--cloud-upload-outlined] text-gray-400"
|
||
></span>
|
||
</div>
|
||
<!-- 头像图片 -->
|
||
<img
|
||
v-if="sourceValue"
|
||
:src="sourceValue"
|
||
alt="avatar"
|
||
class="h-full w-full object-cover"
|
||
/>
|
||
</div>
|
||
<!-- 上传按钮 -->
|
||
<ElButton
|
||
v-if="showBtn"
|
||
class="mx-auto mt-2"
|
||
@click="openModal"
|
||
v-bind="btnProps"
|
||
>
|
||
{{ btnText ? btnText : $t('ui.cropper.selectImage') }}
|
||
</ElButton>
|
||
|
||
<CropperModal
|
||
:size="size"
|
||
:src="sourceValue"
|
||
:upload-api="uploadApi"
|
||
@upload-success="handleUploadSuccess"
|
||
/>
|
||
</div>
|
||
</template>
|