CropperAvatar.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <template>
  2. <div class="user-info-head" @click="open()">
  3. <img v-if="sourceValue" :src="sourceValue" alt="avatar" class="img-circle img-lg" />
  4. <img v-if="!sourceValue" :src="avatar" alt="avatar" class="img-circle img-lg" />
  5. <el-button v-if="showBtn" :class="`${prefixCls}-upload-btn`" @click="open()">
  6. {{ btnText ? btnText : t('cropper.selectImage') }}
  7. </el-button>
  8. <CopperModal
  9. ref="cropperModelRef"
  10. :srcValue="sourceValue"
  11. @upload-success="handleUploadSuccess"
  12. />
  13. </div>
  14. </template>
  15. <script lang="ts" setup>
  16. import { useDesign } from '@/hooks/web/useDesign'
  17. import { propTypes } from '@/utils/propTypes'
  18. import { useI18n } from 'vue-i18n'
  19. import CopperModal from './CopperModal.vue'
  20. import avatar from '@/assets/imgs/avatar.jpg'
  21. defineOptions({ name: 'CropperAvatar' })
  22. const props = defineProps({
  23. width: propTypes.string.def('200px'),
  24. value: propTypes.string.def(''),
  25. showBtn: propTypes.bool.def(true),
  26. btnText: propTypes.string.def('')
  27. })
  28. const emit = defineEmits(['update:value', 'change'])
  29. const sourceValue = ref(props.value)
  30. const { getPrefixCls } = useDesign()
  31. const prefixCls = getPrefixCls('cropper-avatar')
  32. const message = useMessage()
  33. const { t } = useI18n()
  34. const cropperModelRef = ref()
  35. watchEffect(() => {
  36. sourceValue.value = props.value
  37. })
  38. watch(
  39. () => sourceValue.value,
  40. (v: string) => {
  41. emit('update:value', v)
  42. }
  43. )
  44. function handleUploadSuccess({ source, data, filename }) {
  45. sourceValue.value = source
  46. emit('change', { source, data, filename })
  47. message.success(t('cropper.uploadSuccess'))
  48. }
  49. function open() {
  50. cropperModelRef.value.openModal()
  51. }
  52. function close() {
  53. cropperModelRef.value.closeModal()
  54. }
  55. defineExpose({
  56. open,
  57. close
  58. })
  59. </script>
  60. <style lang="scss" scoped>
  61. $prefix-cls: #{$namespace}--cropper-avatar;
  62. .#{$prefix-cls} {
  63. display: inline-block;
  64. text-align: center;
  65. &-image-wrapper {
  66. overflow: hidden;
  67. cursor: pointer;
  68. border: 1px solid;
  69. border-radius: 50%;
  70. img {
  71. width: 100%;
  72. }
  73. }
  74. &-image-mask {
  75. position: absolute;
  76. width: inherit;
  77. height: inherit;
  78. cursor: pointer;
  79. background: rgb(0 0 0 / 40%);
  80. border: inherit;
  81. border-radius: inherit;
  82. opacity: 0;
  83. transition: opacity 0.4s;
  84. ::v-deep(svg) {
  85. margin: auto;
  86. }
  87. }
  88. &-image-mask:hover {
  89. opacity: 40;
  90. }
  91. &-upload-btn {
  92. margin: 10px auto;
  93. }
  94. }
  95. .user-info-head {
  96. position: relative;
  97. display: inline-block;
  98. }
  99. .img-circle {
  100. border-radius: 50%;
  101. }
  102. .img-lg {
  103. width: 120px;
  104. height: 120px;
  105. }
  106. .user-info-head:hover::after {
  107. position: absolute;
  108. inset: 0;
  109. font-size: 24px;
  110. -webkit-font-smoothing: antialiased;
  111. -moz-osx-font-smoothing: grayscale;
  112. font-style: normal;
  113. line-height: 110px;
  114. color: #eee;
  115. cursor: pointer;
  116. background: rgb(0 0 0 / 50%);
  117. border-radius: 50%;
  118. content: '+';
  119. }
  120. </style>