<template>
|
<div :class="getClass" :style="getStyle">
|
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
|
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
|
<Icon
|
icon="ant-design:cloud-upload-outlined"
|
:size="getIconWidth"
|
:style="getImageWrapperStyle"
|
color="#d6d6d6"
|
/>
|
</div>
|
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
|
</div>
|
<a-button
|
:class="`${prefixCls}-upload-btn`"
|
@click="openModal"
|
v-if="showBtn"
|
v-bind="btnProps"
|
>
|
{{ btnText ? btnText : t('component.cropper.selectImage') }}
|
</a-button>
|
|
<CopperModal
|
@register="register"
|
@upload-success="handleUploadSuccess"
|
:uploadApi="uploadApi"
|
:src="sourceValue"
|
/>
|
</div>
|
</template>
|
<script lang="ts">
|
import {
|
defineComponent,
|
computed,
|
CSSProperties,
|
unref,
|
ref,
|
watchEffect,
|
watch,
|
PropType,
|
} from 'vue';
|
import CopperModal from './CopperModal.vue';
|
import { useDesign } from '/@/hooks/web/useDesign';
|
import { useModal } from '/@/components/Modal';
|
import { useMessage } from '/@/hooks/web/useMessage';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
import type { ButtonProps } from '/@/components/Button';
|
import Icon from '@/components/Icon/Icon.vue';
|
|
const props = {
|
width: { type: [String, Number], default: '200px' },
|
value: { type: String },
|
showBtn: { type: Boolean, default: true },
|
btnProps: { type: Object as PropType<ButtonProps> },
|
btnText: { type: String, default: '' },
|
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
|
};
|
|
export default defineComponent({
|
name: 'CropperAvatar',
|
components: { CopperModal, Icon },
|
props,
|
emits: ['update:value', 'change'],
|
setup(props, { emit, expose }) {
|
const sourceValue = ref(props.value || '');
|
const { prefixCls } = useDesign('cropper-avatar');
|
const [register, { openModal, closeModal }] = useModal();
|
const { createMessage } = useMessage();
|
const { t } = useI18n();
|
|
const getClass = computed(() => [prefixCls]);
|
|
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
|
|
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
|
|
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
|
|
const getImageWrapperStyle = computed(
|
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
|
);
|
|
watchEffect(() => {
|
sourceValue.value = props.value || '';
|
});
|
|
watch(
|
() => sourceValue.value,
|
(v: string) => {
|
emit('update:value', v);
|
},
|
);
|
|
function handleUploadSuccess({ source, data }) {
|
sourceValue.value = source;
|
emit('change', { source, data });
|
createMessage.success(t('component.cropper.uploadSuccess'));
|
}
|
|
expose({ openModal: openModal.bind(null, true), closeModal });
|
|
return {
|
t,
|
prefixCls,
|
register,
|
openModal: openModal as any,
|
getIconWidth,
|
sourceValue,
|
getClass,
|
getImageWrapperStyle,
|
getStyle,
|
handleUploadSuccess,
|
};
|
},
|
});
|
</script>
|
|
<style lang="less" scoped>
|
@prefix-cls: ~'@{namespace}-cropper-avatar';
|
|
.@{prefix-cls} {
|
display: inline-block;
|
text-align: center;
|
|
&-image-wrapper {
|
overflow: hidden;
|
border: 1px solid @border-color-base;
|
border-radius: 50%;
|
background: @component-background;
|
cursor: pointer;
|
|
img {
|
width: 100%;
|
}
|
}
|
|
&-image-mask {
|
position: absolute;
|
width: inherit;
|
height: inherit;
|
transition: opacity 0.4s;
|
border: inherit;
|
border-radius: inherit;
|
opacity: 0;
|
background: rgb(0 0 0 / 40%);
|
cursor: pointer;
|
|
::v-deep(svg) {
|
margin: auto;
|
}
|
}
|
|
&-image-mask:hover {
|
opacity: 40;
|
}
|
|
&-upload-btn {
|
margin: 10px auto;
|
}
|
}
|
</style>
|