gaoluyang
昨天 b64a0deae5b5d33f9e20671a68936b27f0b9b00b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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>