index.vue 3.5 KB

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