avatar —— 头像
头像组件要处理三种内容形态:图片、文字、图标,且需要在图片加载失败或地址非法时优雅降级。core/avatar 把「文字提取」和「图片来源判定」做成纯函数——这两个判定跨端必须完全一致,否则会出现「Vue3 显示文字、uniapp 显示破图」的分歧。
- 各端实现
LhAvatar:图片可用时渲染img,否则渲染getAvatarText提取的首字,都没有时渲染图标占位。 - 用户系统里取「姓名首字」做默认头像的场景。
- 不该用它:图片裁剪、上传。这是
LhUpload+ 业务逻辑的事。
import { getAvatarClassNames, getAvatarText, isImageSource } from '@lh-ui/share/core/avatar'import type { LhAvatarState, LhAvatarShape } from '@lh-ui/share/core/avatar'getAvatarText 用 codePointAt(0) + String.fromCodePoint 而不是 str[0],这是一个容易忽略的坑:'😀'[0] 得到的是半个代理对(\uD83D),渲染出来是乱码方块。用码点提取,emoji 和增补平面字符(很多生僻汉字)都安全。
isImageSource 的正则只做宽松的可用性判定,不做安全校验:匹配 http(s)://、data:image/、绝对路径 /、相对路径 ./、../ 开头的字符串视为图片地址。它的用途是「决定要不要尝试渲染 img」,真正的加载失败由 img 的 error 事件兜底(UI 层切到文字态)。
| 函数 | 签名 | 返回 | 作用 |
|---|---|---|---|
getAvatarClassNames |
(state: LhAvatarState) => string[] |
类名数组 | 形状与尺寸修饰 |
getAvatarText |
(source: string) => string |
首字或 '' |
从文本提取展示字 |
isImageSource |
(src: string | undefined) => boolean |
布尔 | 图片地址是否值得尝试渲染 |
边界细节:
getAvatarText('')→'';getAvatarText(' 水墨 ')→'水'(先 trim);getAvatarText('Alice')→'A'。getAvatarText在codePointAt(0)返回 undefined(空串已 trim 掉,理论上不会触发)时返回''。isImageSource(undefined)→false;isImageSource('')→false。
export type LhAvatarShape = 'circle' | 'square'
export interface LhAvatarState { size: LhComponentSize shape: LhAvatarShape}core/avatar 不输出 aria 属性包,因为头像的处理方式取决于内容形态:
- 图片头像:用原生
<img>,alt必填(如「张三的头像」)。不要留空 alt——空 alt 表示「装饰性图片」,而头像通常承载身份信息。 - 文字头像:文字本身就是可读内容,无需额外处理。
- 图标头像(匿名用户占位):图标走
getIconAria({ decorative: false, label: '匿名用户' })。
import { getAvatarText, isImageSource } from '@lh-ui/share/core/avatar'
getAvatarText('王小明') // '王'getAvatarText('Alice Smith') // 'A'getAvatarText('😀哈哈') // '😀'isImageSource('https://a.com/1.png') // trueisImageSource('data:image/png;base64,...') // trueisImageSource(undefined) // false<!-- Vue3 --><div :class="getAvatarClassNames({ size, shape })"> <img v-if="isImage && !error" :src="src" :alt="alt" @error="error = true" /> <span v-else-if="text" class="lh-avatar__text">{{ text }}</span> <LhIcon v-else name="user" :decorative="false" label="匿名用户" /></div>// Reactconst text = getAvatarText(name)const showImg = isImageSource(src) && !imgErrorreturn ( <div className={getAvatarClassNames({ size, shape }).join(' ')}> {showImg ? <img src={src} alt={alt} onError={() => setImgError(true)} /> : text ? <span className="lh-avatar__text">{text}</span> : <LhIcon name="user" decorative={false} label="匿名用户" />} </div>)// uniapp:<image> 组件有 @error 事件,逻辑同上;// 文字头像在非 h5 端注意字体回退(生僻字可能缺字形)| 类名 | 语义 |
|---|---|
.lh-avatar |
基础类:固定宽高、溢出隐藏、居中 |
.lh-avatar--{mini|small|medium|large|x-large} |
尺寸档 |
.lh-avatar--circle / .lh-avatar--square |
形状(圆角程度) |
.lh-avatar__img |
图片:铺满 + object-fit |
.lh-avatar__text |
文字:居中、白色、按尺寸缩放 |
.lh-avatar__icon |
图标占位 |
头像底色默认是主色令牌;多头像场景(如头像组)各端 UI 层可按名字哈希上色,core 不参与。
- 不要用
str[0]或str.charAt(0)在 UI 层自己取首字——emoji 与生僻字会截断成乱码,统一用getAvatarText。 isImageSource返回 true 不代表图片存在,只代表「值得一试」。onError兜底是必须的。square不是直角,而是小圆角(视觉调和),直角头像在设计中显得过于尖锐。- 图片头像的
object-fit默认cover,人脸可能被裁掉边缘——业务上常需要object-position微调,这在各端 UI 层处理。
@lh-ui/share/core/avatar → getAvatarText( )返回值
(填好参数后点运行)Copyright © 2024-2026 风默文学 | 宁ICP备2024006574号 |
苏公网安备32040002010537