index.vue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <template>
  2. <div
  3. class="avatar-container"
  4. :style="{
  5. width: avatarSize,
  6. height: avatarSize,
  7. borderRadius: avatarBorderRadius,
  8. }"
  9. >
  10. <template v-if="isUniFrameWork">
  11. <image
  12. v-if="!loadErrorInUniapp"
  13. class="avatar-image"
  14. :src="avatarImageUrl || defaultAvatarUrl"
  15. @load="avatarLoadSuccess"
  16. @error="avatarLoadFailed"
  17. />
  18. <image
  19. v-else
  20. class="avatar-image"
  21. :src="defaultAvatarUrl"
  22. @load="avatarLoadSuccess"
  23. @error="avatarLoadFailed"
  24. />
  25. </template>
  26. <img
  27. v-else
  28. class="avatar-image"
  29. :src="avatarImageUrl || defaultAvatarUrl"
  30. @load="avatarLoadSuccess"
  31. @error="avatarLoadFailed"
  32. />
  33. <div
  34. v-if="useAvatarSkeletonAnimation && !isImgLoaded"
  35. :class="{
  36. placeholder: true,
  37. hidden: isImgLoaded,
  38. 'skeleton-animation': useAvatarSkeletonAnimation,
  39. }"
  40. />
  41. </div>
  42. </template>
  43. <script setup lang="ts">
  44. import { ref, toRefs } from "../../../adapter-vue";
  45. import { isUniFrameWork } from "../../../utils/env";
  46. interface IProps {
  47. url: string;
  48. size?: string;
  49. borderRadius?: string;
  50. useSkeletonAnimation?: boolean;
  51. }
  52. interface IEmits {
  53. (key: "onLoad", e: Event): void;
  54. (key: "onError", e: Event): void;
  55. }
  56. const defaultAvatarUrl = ref(
  57. "https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png"
  58. );
  59. const emits = defineEmits<IEmits>();
  60. const props = withDefaults(defineProps<IProps>(), {
  61. // uniapp vue2 does not support constants in defineProps
  62. url: "https://web.sdk.qcloud.com/component/TUIKit/assets/avatar_21.png",
  63. size: "36px",
  64. borderRadius: "50%",
  65. useSkeletonAnimation: false,
  66. });
  67. const {
  68. size: avatarSize,
  69. url: avatarImageUrl,
  70. borderRadius: avatarBorderRadius,
  71. useSkeletonAnimation: useAvatarSkeletonAnimation,
  72. } = toRefs(props);
  73. let reloadAvatarTime = 0;
  74. const isImgLoaded = ref<boolean>(false);
  75. const loadErrorInUniapp = ref<boolean>(false);
  76. function avatarLoadSuccess(e: Event) {
  77. isImgLoaded.value = true;
  78. emits("onLoad", e);
  79. }
  80. function avatarLoadFailed(e: Event) {
  81. reloadAvatarTime += 1;
  82. if (reloadAvatarTime > 3) {
  83. return;
  84. }
  85. if (isUniFrameWork) {
  86. loadErrorInUniapp.value = true;
  87. } else {
  88. (e.currentTarget as HTMLImageElement).src = defaultAvatarUrl.value;
  89. }
  90. emits("onError", e);
  91. }
  92. </script>
  93. <style scoped lang="scss">
  94. :not(not) {
  95. display: flex;
  96. flex-direction: column;
  97. box-sizing: border-box;
  98. min-width: 0;
  99. }
  100. .avatar-container {
  101. position: relative;
  102. justify-content: center;
  103. align-items: center;
  104. overflow: hidden;
  105. flex: 0 0 auto;
  106. .placeholder {
  107. position: absolute;
  108. top: 0;
  109. left: 0;
  110. width: 100%;
  111. height: 100%;
  112. background-color: #ececec;
  113. transition: opacity 0.3s, background-color 0.1s ease-out;
  114. &.skeleton-animation {
  115. animation: breath 2s linear 0.3s infinite;
  116. }
  117. &.hidden {
  118. opacity: 0;
  119. }
  120. }
  121. .avatar-image {
  122. width: 100%;
  123. height: 100%;
  124. border-radius: 50%;
  125. }
  126. }
  127. @keyframes breath {
  128. 50% {
  129. /* stylelint-disable-next-line scss/no-global-function-names */
  130. background-color: darken(#ececec, 10%);
  131. }
  132. }
  133. </style>