gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
src/views/im/home/components/user/user-avatar.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,134 @@
<script lang="ts" setup>
import type { User } from '../../types'
import { computed } from 'vue'
import { Image } from 'ant-design-vue'
import { ImFriendAddSource } from '../../../utils/constants'
import { getAvatarBgColor, getAvatarText } from '../../../utils/user'
import { useImUiStore } from '../../store/uiStore'
defineOptions({ name: 'ImUserAvatar', inheritAttrs: false })
const props = withDefaults(
  defineProps<{
    addSource?: number // åŠ å¥½å‹æ¥æºï¼›ç‚¹å¤´åƒå¼¹ UserInfoCard æ—¶é€ä¼ ç»™ FriendAddDialog(默认 1=搜索)
    addSourceExtra?: string // åŠ å¥½å‹æ¥æºé™„åŠ ï¼šaddSource=2(群聊)时传群名,话术拼为「我是 XX ç¾¤çš„ YY」
    clickable?: boolean // æ˜¯å¦ç‚¹å‡»å¼¹å‡º UserInfoCard;默认 true
    id?: number | string // ç”¨æˆ· id;传了才能点开名片
    name?: string // ç”¨æˆ·åï¼ˆè‰²å¡æ–‡å­— + åç‰‡å¤‡ç”¨ï¼‰
    previewable?: boolean // æ˜¯å¦ç‚¹å¤´åƒç›´æŽ¥æ”¾å¤§é¢„览;开启后忽略 clickable,不再弹名片
    previewZIndex?: number // é¢„览层 z-index;放在高 z-index å¼¹å±‚(如 UserInfoCard)里时需手动抬高
    radius?: string // åœ†è§’,支持 CSS é•¿åº¦ï¼›é»˜è®¤ 15% æ–¹å—小圆角(参考微信)
    size?: number // å°ºå¯¸ï¼ˆpx),正方形;优先于 width/height
    url?: string // å¤´åƒå›¾ç‰‡ URL;为空时走色卡文字
    user?: User // é¢å¤–的用户信息,传了点击就不用现拉接口(弹名片用)
  }>(),
  {
    addSourceExtra: undefined,
    clickable: true,
    id: undefined,
    name: undefined,
    previewable: false,
    previewZIndex: 2000,
    radius: '15%',
    size: 42,
    url: undefined,
    user: undefined,
    addSource: ImFriendAddSource.SEARCH
  }
)
const uiStore = useImUiStore()
const imgStyle = computed(() => ({
  width: `${props.size}px`,
  height: `${props.size}px`,
  borderRadius: props.radius
}))
const textStyle = computed(() => ({
  width: `${props.size}px`,
  height: `${props.size}px`,
  fontSize: `${Math.floor(props.size * (avatarText.value.length > 1 ? 0.34 : 0.42))}px`,
  background: textColor.value,
  borderRadius: props.radius
}))
/** è‰²å¡é¦–字:中文取 1 ä¸ªå­—、英文 / æ‹‰ä¸å–前 2 ä¸ªå­—母 */
const avatarText = computed(() => getAvatarText(props.name))
/** è‰²å¡åº•色:按昵称 charCode å“ˆå¸Œå–调色板色 */
const textColor = computed(() => getAvatarBgColor(props.name))
/** å¤´åƒç‚¹å‡»ï¼špreviewable èµ° el-image é¢„览不弹名片;否则按 user / id ä»»ä¸€å…¥å‚打开名片 */
function handleClick(e: MouseEvent) {
  if (props.previewable) {
    return
  }
  if (!props.clickable) {
    return
  }
  // æƒ…况一:有预传 user ä¿¡æ¯ï¼šå°±ç›´æŽ¥ç”¨ï¼Œçœä¸€æ¬¡æŽ¥å£
  if (props.user) {
    uiStore.openUserInfoCardAtEvent(props.user, e, props.addSource, props.addSourceExtra)
    return
  }
  // æƒ…况二:无预传 user ä¿¡æ¯ï¼šæ‰“开名片,传最小必要信息(id + æ˜µç§° + å¤´åƒï¼‰ï¼Œä½ç½®åœ¨é¼ æ ‡å³ä¾§
  const numId = Number(props.id)
  if (!numId || numId <= 0) {
    return
  }
  uiStore.openUserInfoCardAtEvent(
    {
      id: numId,
      nickname: props.name,
      avatar: props.url
    },
    e,
    props.addSource,
    props.addSourceExtra
  )
}
</script>
<template>
  <!--
    é€šç”¨ç”¨æˆ·å¤´åƒç»„ä»¶
    - æœ‰ url æ—¶å±•示图片;无 url æ—¶å±•示色卡 + é¦–字母/首字
    - ç‚¹å‡»é»˜è®¤è§¦å‘ UserInfoCard(clickable)
    - previewable=true æ—¶æ”¹ä¸ºç‚¹å¤´åƒç›´æŽ¥æ”¾å¤§é¢„览(用于名片 / è¯¦æƒ…页等大头像位)
  -->
  <div
    class="relative inline-flex"
    :style="{ cursor: clickable && !previewable ? 'pointer' : 'default' }"
    v-bind="$attrs"
    @click="handleClick"
  >
    <Image
      v-if="url && previewable"
      class="block overflow-hidden"
      :src="url"
      :preview="{ src: url, zIndex: previewZIndex }"
      :style="imgStyle"
    />
    <img
      v-else-if="url"
      class="block overflow-hidden object-cover"
      :src="url"
      :style="imgStyle"
      loading="lazy"
      :alt="name || 'avatar'"
    />
    <div
      v-else
      class="flex items-center justify-center text-white font-medium select-none"
      :style="textStyle"
    >
      {{ avatarText }}
    </div>
    <!-- å…è®¸å¤–部插入装饰,如群聊角标 -->
    <slot></slot>
  </div>
</template>