CropperAvatar.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <template>
  2. <div :class="getClass" :style="getStyle">
  3. <div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
  4. <div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
  5. <Icon
  6. icon="ant-design:cloud-upload-outlined"
  7. :size="getIconWidth"
  8. :style="getImageWrapperStyle"
  9. color="#d6d6d6"
  10. />
  11. </div>
  12. <img :src="sourceValue" v-if="sourceValue" alt="avatar" />
  13. </div>
  14. <a-button
  15. :class="`${prefixCls}-upload-btn`"
  16. @click="openModal"
  17. v-if="showBtn"
  18. v-bind="btnProps"
  19. >
  20. {{ btnText ? btnText : t('component.cropper.selectImage') }}
  21. </a-button>
  22. <CopperModal
  23. @register="register"
  24. @upload-success="handleUploadSuccess"
  25. :uploadApi="uploadApi"
  26. :src="sourceValue"
  27. />
  28. </div>
  29. </template>
  30. <script lang="ts">
  31. import {
  32. defineComponent,
  33. computed,
  34. CSSProperties,
  35. unref,
  36. ref,
  37. watchEffect,
  38. watch,
  39. PropType,
  40. } from 'vue';
  41. import CopperModal from './CopperModal.vue';
  42. import { useDesign } from '/@/hooks/web/useDesign';
  43. import { useModal } from '/@/components/Modal';
  44. import { useMessage } from '/@/hooks/web/useMessage';
  45. import { useI18n } from '/@/hooks/web/useI18n';
  46. import type { ButtonProps } from '/@/components/Button';
  47. import Icon from '/@/components/Icon';
  48. const props = {
  49. width: { type: [String, Number], default: '200px' },
  50. value: { type: String },
  51. showBtn: { type: Boolean, default: true },
  52. btnProps: { type: Object as PropType<ButtonProps> },
  53. btnText: { type: String, default: '' },
  54. uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
  55. };
  56. export default defineComponent({
  57. name: 'CropperAvatar',
  58. components: { CopperModal, Icon },
  59. props,
  60. emits: ['update:value', 'change'],
  61. setup(props, { emit, expose }) {
  62. const sourceValue = ref(props.value || '');
  63. const { prefixCls } = useDesign('cropper-avatar');
  64. const [register, { openModal, closeModal }] = useModal();
  65. const { createMessage } = useMessage();
  66. const { t } = useI18n();
  67. const getClass = computed(() => [prefixCls]);
  68. const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
  69. const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
  70. const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
  71. const getImageWrapperStyle = computed(
  72. (): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) }),
  73. );
  74. watchEffect(() => {
  75. sourceValue.value = props.value || '';
  76. });
  77. watch(
  78. () => sourceValue.value,
  79. (v: string) => {
  80. emit('update:value', v);
  81. },
  82. );
  83. function handleUploadSuccess({ source, data }) {
  84. sourceValue.value = source;
  85. emit('change', { source, data });
  86. createMessage.success(t('component.cropper.uploadSuccess'));
  87. }
  88. expose({ openModal: openModal.bind(null, true), closeModal });
  89. return {
  90. t,
  91. prefixCls,
  92. register,
  93. openModal: openModal as any,
  94. getIconWidth,
  95. sourceValue,
  96. getClass,
  97. getImageWrapperStyle,
  98. getStyle,
  99. handleUploadSuccess,
  100. };
  101. },
  102. });
  103. </script>
  104. <style lang="less" scoped>
  105. @prefix-cls: ~'@{namespace}-cropper-avatar';
  106. .@{prefix-cls} {
  107. display: inline-block;
  108. text-align: center;
  109. &-image-wrapper {
  110. overflow: hidden;
  111. border: 1px solid @border-color-base;
  112. border-radius: 50%;
  113. background: @component-background;
  114. cursor: pointer;
  115. img {
  116. width: 100%;
  117. }
  118. }
  119. &-image-mask {
  120. position: absolute;
  121. width: inherit;
  122. height: inherit;
  123. transition: opacity 0.4s;
  124. border: inherit;
  125. border-radius: inherit;
  126. opacity: 0;
  127. background: rgb(0 0 0 / 40%);
  128. cursor: pointer;
  129. ::v-deep(svg) {
  130. margin: auto;
  131. }
  132. }
  133. &-image-mask:hover {
  134. opacity: 40;
  135. }
  136. &-upload-btn {
  137. margin: 10px auto;
  138. }
  139. }
  140. </style>