index.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <view class="user-avatar" :class="customClass" :style="wrapperStyle">
  3. <view
  4. class="avatar-box"
  5. :style="boxStyle"
  6. >
  7. <!-- 图片模式:加载正常 -->
  8. <image
  9. v-if="!imgError && src"
  10. class="avatar-img"
  11. :src="src"
  12. :mode="mode"
  13. @error="onImgError"
  14. />
  15. <!-- 文字模式:加载失败或无图片 -->
  16. <view v-else class="avatar-text" :style="textStyle">
  17. <text>{{ displayText }}</text>
  18. </view>
  19. </view>
  20. <!-- 徽章(可选) -->
  21. <image
  22. v-if="badgeSrc"
  23. class="avatar-badge"
  24. :src="badgeSrc"
  25. :style="badgeStyle"
  26. />
  27. </view>
  28. </template>
  29. <script setup>
  30. import { ref, computed } from 'vue'
  31. const props = defineProps({
  32. // 头像图片地址
  33. src: {
  34. type: String,
  35. default: ''
  36. },
  37. // 用户名称(取第一个字)
  38. name: {
  39. type: String,
  40. default: ''
  41. },
  42. // 头像大小(rpx)
  43. size: {
  44. type: Number,
  45. default: 80
  46. },
  47. // 字体大小倍数(相对size),默认0.4
  48. fontRatio: {
  49. type: Number,
  50. default: 0.4
  51. },
  52. // 背景色
  53. bgColor: {
  54. type: String,
  55. default: '#4080FF'
  56. },
  57. // 文字颜色
  58. textColor: {
  59. type: String,
  60. default: '#FFFFFF'
  61. },
  62. // 图片裁剪模式
  63. mode: {
  64. type: String,
  65. default: 'aspectFill'
  66. },
  67. // 圆角值(默认50%圆形)
  68. radius: {
  69. type: String,
  70. default: '50%'
  71. },
  72. // 边框
  73. border: {
  74. type: String,
  75. default: ''
  76. },
  77. // 徽章图片
  78. badgeSrc: {
  79. type: String,
  80. default: ''
  81. },
  82. // 徽章大小(rpx)
  83. badgeSize: {
  84. type: Number,
  85. default: 0
  86. },
  87. // 自定义样式类
  88. customClass: {
  89. type: String,
  90. default: ''
  91. }
  92. })
  93. // 图片是否加载失败
  94. const imgError = ref(false)
  95. // 显示的文字(名称第一个字)
  96. const displayText = computed(() => {
  97. if (!props.name) return '?'
  98. // 取第一个字符
  99. return props.name.charAt(0)
  100. })
  101. // 图片加载失败回调
  102. const onImgError = () => {
  103. imgError.value = true
  104. }
  105. // 外层容器样式
  106. const wrapperStyle = computed(() => {
  107. const sizePx = (props.size / 2) + 'px'
  108. return {
  109. width: sizePx,
  110. height: sizePx
  111. }
  112. })
  113. // 头像盒子样式
  114. const boxStyle = computed(() => {
  115. const sizePx = (props.size / 2) + 'px'
  116. const style = {
  117. width: sizePx,
  118. height: sizePx,
  119. borderRadius: props.radius
  120. }
  121. if (props.border) {
  122. style.border = props.border
  123. }
  124. return style
  125. })
  126. // 文字样式
  127. const textStyle = computed(() => {
  128. const fontSize = (props.size * props.fontRatio / 2) + 'px'
  129. return {
  130. fontSize: fontSize,
  131. backgroundColor: props.bgColor,
  132. color: props.textColor
  133. }
  134. })
  135. // 徽章样式
  136. const badgeStyle = computed(() => {
  137. if (!props.badgeSize) return {}
  138. const sizePx = (props.badgeSize / 2) + 'px'
  139. return {
  140. width: sizePx,
  141. height: sizePx
  142. }
  143. })
  144. </script>
  145. <style scoped>
  146. .user-avatar {
  147. position: relative;
  148. flex-shrink: 0;
  149. }
  150. .avatar-box {
  151. overflow: hidden;
  152. width: 100%;
  153. height: 100%;
  154. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
  155. }
  156. .avatar-img {
  157. width: 100%;
  158. height: 100%;
  159. display: block;
  160. }
  161. .avatar-text {
  162. width: 100%;
  163. height: 100%;
  164. display: flex;
  165. align-items: center;
  166. justify-content: center;
  167. font-weight: 600;
  168. }
  169. .avatar-badge {
  170. position: absolute;
  171. bottom: 0;
  172. right: 0;
  173. transform: translate(20%, 20%);
  174. z-index: 1;
  175. }
  176. </style>