移动端导航头像

头像Avatar

用一张图或一两个字符代表一个主体(用户、企业、群组);移动端列表与详情页里大量出现,必须能单手扫读,因此尺寸只有三档、形状只有两种

数据展示 规格 23 · 头像 Avatar 6 端实现 触摸优先

引入(H5 原生;其余 5 端见「快速开始」)
<!-- ① 令牌:PC 令牌 + 移动端 --kole-m-* 合成单文件,引一次 -->
<link rel="stylesheet" href="kole-ui/mobile/tokens.css">

<!-- ② 本组件样式(全量则用 kole-ui/mobile/components/index.css) -->
<link rel="stylesheet" href="kole-ui/mobile/components/mobile-avatar.css">

<!-- ③ 结构照抄下方任一演示块(类名与 6 端实现一致) -->

演示

每个演示都是真实渲染:预览帧加载 frameworks-mobile/Avatar.html?demo=<id>(只显示该演示块),代码是该演示块在演示页里的原文,可复制。全部演示同屏可看 演示页 ↗。

01 组件类型

三档尺寸size=small|default|large

small 32 / default 40 / large 56,文字字号随尺寸放大,小尺寸下仍可读。

查看代码(演示页原文 · 8 行)
frameworks-mobile/Avatar.html · size
<section class="demo-block" data-demo="size">
  <p class="demo-label">三档尺寸(small 32 / default 40 / large 56,文字随尺寸放大)</p>
  <div class="demo-box">
    <span class="kole-m-avatar kole-m-avatar--small kole-m-avatar--circle" role="img" aria-label="王小明 头像" data-assert="avatar-size-small"><span class="kole-m-avatar__text">王</span></span>
    <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="李小红 头像" data-assert="avatar-size-default"><span class="kole-m-avatar__text">李</span></span>
    <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--circle" role="img" aria-label="赵小刚 头像" data-assert="avatar-size-large"><span class="kole-m-avatar__text">赵</span></span>
  </div>
</section>
两种形状shape=circle|square

circle 用于人(成员、好友),square 用于企业或群组(标志、团队)。

查看代码(演示页原文 · 8 行)
frameworks-mobile/Avatar.html · shape
<section class="demo-block" data-demo="shape">
  <p class="demo-label">两种形状(circle 用于人 / square 用于企业、群组)</p>
  <div class="demo-box">
    <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="张 头像" data-assert="avatar-circle"><span class="kole-m-avatar__text">张</span></span>
    <span class="kole-m-avatar kole-m-avatar--square" role="img" aria-label="云启科技 标志" data-assert="avatar-square"><span class="kole-m-avatar__text">云启</span></span>
    <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--square" role="img" aria-label="设计中心 标志"><span class="kole-m-avatar__text">设计</span></span>
  </div>
</section>
图片头像type=image

type=image 时图片铺满容器并按形状裁切,语义交给 <img alt>。

查看代码(演示页原文 · 13 行)
frameworks-mobile/Avatar.html · image
<section class="demo-block" data-demo="image">
  <p class="demo-label">图片头像(type=image:铺满容器并按形状裁切)</p>
  <div class="demo-box">
    <span class="kole-m-avatar kole-m-avatar--circle" data-assert="avatar-image-circle">
      <img class="kole-m-avatar__image" alt="示例用户 头像"
           src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCI+PHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjMkY1NEVCIi8+PGNpcmNsZSBjeD0iNDAiIGN5PSIzMCIgcj0iMTMiIGZpbGw9IiNGRkZGRkYiLz48cGF0aCBkPSJNMTMgNzRjNC0xNSAxNC0yMiAyNy0yMnMyMyA3IDI3IDIyeiIgZmlsbD0iI0ZGRkZGRiIvPjwvc3ZnPg==">
    </span>
    <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--square" data-assert="avatar-image-square">
      <img class="kole-m-avatar__image" alt="示例企业 标志"
           src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCI+PHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjMkY1NEVCIi8+PGNpcmNsZSBjeD0iNDAiIGN5PSIzMCIgcj0iMTMiIGZpbGw9IiNGRkZGRkYiLz48cGF0aCBkPSJNMTMgNzRjNC0xNSAxNC0yMiAyNy0yMnMyMyA3IDI3IDIyeiIgZmlsbD0iI0ZGRkZGRiIvPjwvc3ZnPg==">
    </span>
  </div>
</section>

02 组件状态

图片兜底状态 fallback

状态 fallback:图片不可用时退回文字,底色转中性底,绝不出现裂图。

查看代码(演示页原文 · 12 行)
frameworks-mobile/Avatar.html · fallback
<section class="demo-block" data-demo="fallback">
  <p class="demo-label">图片兜底(状态 fallback:图片不可用时退回文字,不出现裂图)</p>
  <div class="demo-box">
    <span class="kole-m-avatar kole-m-avatar--circle is-fallback" role="img" aria-label="头像加载失败,显示 陈" data-assert="avatar-fallback">
      <span class="kole-m-avatar__text">陈</span>
    </span>
    <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="周 头像">
      <span class="kole-m-avatar__text">周</span>
    </span>
    <span class="demo-tag">图片失败 → 文字</span>
  </div>
</section>
带角标slots.badge

角标位挂在线状态或未读数,绝对定位不改变头像尺寸。

查看代码(演示页原文 · 13 行)
frameworks-mobile/Avatar.html · badge
<section class="demo-block" data-demo="badge">
  <p class="demo-label">带角标(badge 位:挂在线状态或未读数,不改变头像尺寸)</p>
  <div class="demo-box">
    <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="吴 头像,在线" data-assert="avatar-badge-online">
      <span class="kole-m-avatar__text">吴</span>
      <span class="kole-m-avatar__badge" aria-hidden="true"></span>
    </span>
    <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--square" role="img" aria-label="研发一组 标志,在线">
      <span class="kole-m-avatar__text">研发</span>
      <span class="kole-m-avatar__badge" aria-hidden="true"></span>
    </span>
  </div>
</section>
禁用disabled=true

状态 disabled:置灰用于已离职或已停用的主体。

查看代码(演示页原文 · 11 行)
frameworks-mobile/Avatar.html · disabled
<section class="demo-block" data-demo="disabled">
  <p class="demo-label">禁用(状态 disabled:置灰,用于已离职或已停用的主体)</p>
  <div class="demo-box">
    <span class="kole-m-avatar kole-m-avatar--circle is-disabled" role="img" aria-label="孙 头像(已停用)" data-assert="avatar-disabled" aria-disabled="true">
      <span class="kole-m-avatar__text">孙</span>
    </span>
    <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="钱 头像">
      <span class="kole-m-avatar__text">钱</span>
    </span>
  </div>
</section>

API

props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs

Props

名称类型默认值说明必传
size'small' | 'default' | 'large''default'变体 size:三档尺寸 32 / 40 / 56px,文字随尺寸放大(规格 §23.3)N
shape'circle' | 'square''circle'变体 shape:circle 用于人,square 用于企业或群组(规格 §23.3)N
type'text' | 'image''text'变体 type:文字头像 / 图片头像,图片失败退回文字(规格 §23.3)N
srcstring''图片地址,type=image 时必填(规格 §23.2 image)N
altstring''可访问名:图片头像作为 <img alt>,文字头像作为根节点 aria-label(规格 §23.6)N
disabledbooleanfalse状态 disabled:置灰(如成员已离职)(规格 §23.4)N

「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。

事件

名称参数说明
error(event)图片加载失败时触发一次;是否替换资源由宿主决定(规格 §23.5)

插槽

名称说明
default文字内容(姓名首字或简称)(规格 §23.2 text)
badge右下角角标位,挂在线状态或未读数(规格 §23.2 badge)

CSS 变量

组件级变量(在组件样式表里定义)。业务侧可在自己的作用域内覆盖,不必改组件源码。

名称默认值说明
--kole-m-avatar-size40px三档尺寸:small 32 / default 40 / large 56
--kole-m-avatar-radius50%形状:circle 50% / square 圆角方形
--kole-m-avatar-font-size14px文字随尺寸放大,保证小尺寸下仍可读
--kole-m-avatar-badge-size10px右下角角标位直径
--kole-m-avatar-size32px组件内部默认值,可在业务侧覆盖
--kole-m-avatar-font-size12px组件内部默认值,可在业务侧覆盖
--kole-m-avatar-badge-size8px组件内部默认值,可在业务侧覆盖
--kole-m-avatar-size56px组件内部默认值,可在业务侧覆盖
--kole-m-avatar-font-size18px组件内部默认值,可在业务侧覆盖
--kole-m-avatar-badge-size12px组件内部默认值,可在业务侧覆盖

何时使用

  • 用一张图或一两个字符代表一个主体(用户、企业、群组);移动端列表与详情页里大量出现,必须能单手扫读,因此尺寸只有三档、形状只有两种
  • 图片不可用时必须立刻退回文字,不能出现裂图
  • 头像本身不是按钮;可点时必须由宿主包一层原生 button 或 a,热区不小于 44×44
  • 图片 alt 或根节点 aria-label 必填其一,读屏读「姓名 + 头像」
  • 图片加载失败只触发一次 error 事件,是否替换资源由宿主决定

交互与触控

  • 头像本身不是按钮;可点时必须由宿主包一层原生 button 或 a,热区不小于 44×44
  • 图片加载失败只触发一次 error 事件,是否替换资源由宿主决定

无障碍

  • 文字头像根节点 role="img" 并带 aria-label(内容为姓名)
  • 图片头像直接用 <img alt>,alt 为空时视为装饰并对读屏隐藏
  • 角标不改变头像的可访问名

从「该用哪一个」的角度区分;PC 端的对应实现见 PC 文档站。

组件何时用它而不是本组件
列表List头像作为列表行的前缀位时用列表承载整行;只有单张头像时用本组件
徽标Badge需要数字角标时用徽标组件包裹头像;只表示在线状态用本组件自带的 badge 位
标签Tag要用文字标注状态或分类时用标签;头像只承载主体身份

规格未定 / 禁止发明

类别条目
禁止发明图片裁剪的 focal point(人脸居中)算法
禁止发明角标位置随形状(圆 / 方)的微调规则
规格未定文字头像的底色是否按名字散列取多色
规格未定姓名超过两个汉字时的截断规则

结构(anatomy)

字段说明
avatar根元素,正方形圆角容器,尺寸由 size 决定
image图片,铺满容器并按形状裁切
text文字内容(姓名首字或简称),图片缺失时它就是主体
fallback兜底节点,图片加载失败后退回的形状(文字或图标)
badge可选右下角角标位,挂在线状态或未读数

变体维度与类名映射

类名映射由构建脚本从契约 variantClasses 生成,并被 verify:mobile-docs 逐条对照组件 CSS 校验(类/变量必须真实存在)。

维度取值对应类名 / 变量
sizesmall / default / large
small .kole-m-avatar--small
default (由数据驱动,无专属类)
large .kole-m-avatar--large
shapecircle / square
circle .kole-m-avatar--circle
square .kole-m-avatar--square
typetext / image
text (由数据驱动,无专属类)
image .kole-m-avatar__image

代表变体

变体标签
size=default · shape=circle · type=text文字头像(圆形,用户默认形态)
size=large · shape=square · type=image图片头像(圆角方形,企业 / 群组)
size=small · shape=circle · type=text小尺寸头像(列表行前缀位)
size=default · shape=circle · type=image图片加载失败退回文字(状态 fallback)

用到的令牌

构建时从本组件样式表扫描得出。蓝色为移动端自有令牌,绿色为继承的 PC 令牌(改一处两端生效)。

--kole-color-brand --kole-color-card-bg --kole-color-disabled-bg --kole-color-focus-ring --kole-color-success --kole-color-table-header-bg --kole-color-text-disabled --kole-color-text-inverse --kole-color-text-secondary --kole-font-family --kole-radius-medium --kole-m-avatar-badge-size --kole-m-avatar-font-size --kole-m-avatar-radius --kole-m-avatar-size

6 端源码

同一组件的六份实现(生产环境的类名与结构一致,差异只在技术栈写法与单位)。点开查看,右侧可复制。

frameworks-mobile/Avatar.css · 纯样式(CSS) · 94 行
frameworks-mobile/Avatar.css
/* Kole UI Mobile · Avatar 样式 — 对齐移动端规格 §23
   头像:三档尺寸 × 两种形状 × 文字 / 图片两种类型;
   文字头像用品牌底 + 反色字(亮色 5.85:1 / 暗色 5.87:1),图片失败退回文字(is-fallback)。 */

.kole-m-avatar {
  --kole-m-avatar-size: 40px;         /* 三档尺寸:small 32 / default 40 / large 56 */
  --kole-m-avatar-radius: 50%;        /* 形状:circle 50% / square 圆角方形 */
  --kole-m-avatar-font-size: 14px;    /* 文字随尺寸放大,保证小尺寸下仍可读 */
  --kole-m-avatar-badge-size: 10px;   /* 右下角角标位直径 */
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: var(--kole-m-avatar-size);
  height: var(--kole-m-avatar-size);
  overflow: hidden;
  border-radius: var(--kole-m-avatar-radius);
  background: var(--kole-color-brand);
  color: var(--kole-color-text-inverse);
  font-family: var(--kole-font-family);
  font-size: var(--kole-m-avatar-font-size);
  line-height: 1;
  user-select: none;
}

/* 变体 size:三档(small 32 / default 40 / large 56) */
.kole-m-avatar--small {
  --kole-m-avatar-size: 32px;
  --kole-m-avatar-font-size: 12px;
  --kole-m-avatar-badge-size: 8px;
}

.kole-m-avatar--large {
  --kole-m-avatar-size: 56px;
  --kole-m-avatar-font-size: 18px;
  --kole-m-avatar-badge-size: 12px;
}

/* 变体 shape:circle 用于人,square 用于企业 / 群组 */
.kole-m-avatar--circle { --kole-m-avatar-radius: 50%; }
.kole-m-avatar--square { --kole-m-avatar-radius: var(--kole-radius-medium); }

/* 变体 type=image:图片铺满容器并按形状裁切 */
.kole-m-avatar__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 变体 type=text:一到两个字符居中 */
.kole-m-avatar__text {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* 状态 fallback:图片不可用时退回文字,底色转为中性底 */
.kole-m-avatar.is-fallback {
  background: var(--kole-color-table-header-bg);
  color: var(--kole-color-text-secondary);
}

/* 状态 disabled:置灰(如成员已离职) */
.kole-m-avatar.is-disabled {
  background: var(--kole-color-disabled-bg);
  color: var(--kole-color-text-disabled);
}

/* 角标位:挂在线状态或未读数,内容由宿主插槽提供 */
.kole-m-avatar__badge {
  position: absolute;
  bottom: 0;
  right: 0;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--kole-m-avatar-badge-size);
  height: var(--kole-m-avatar-badge-size);
  border: 2px solid var(--kole-color-card-bg);
  border-radius: 50%;
  background: var(--kole-color-success);
}

/* 头像本身是内容不是控件;宿主把它包进 button / a 时才需要键盘焦点环 */
.kole-m-avatar:focus-visible {
  outline: 2px solid var(--kole-color-focus-ring);
  outline-offset: 2px;
}
frameworks-mobile/Avatar.html · H5 原生(无框架) · 116 行
frameworks-mobile/Avatar.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Kole UI Mobile · Avatar(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Avatar.css">
<style>
  body { margin: 0; background: var(--kole-color-page-bg); font-family: var(--kole-font-family); color: var(--kole-color-text-body); }
  .demo { max-width: 375px; margin: 0 auto; padding: var(--kole-m-gutter) 0; }
  .demo-label { margin: 0; padding: var(--kole-space-12) var(--kole-m-gutter) var(--kole-space-8);
    font-size: var(--kole-m-font-size-label); color: var(--kole-color-text-secondary); }
  .demo-box { display: flex; align-items: center; gap: var(--kole-space-16); padding: var(--kole-space-24) var(--kole-m-gutter);
    background: var(--kole-color-card-bg); border-block: 1px solid var(--kole-color-border); }
  .demo-tag { display: inline-flex; align-items: center; height: 22px; padding: 0 8px; border-radius: 11px;
    background: var(--kole-color-brand-bg); color: var(--kole-color-brand); font-size: var(--kole-m-font-size-caption); }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="size">
    <p class="demo-label">三档尺寸(small 32 / default 40 / large 56,文字随尺寸放大)</p>
    <div class="demo-box">
      <span class="kole-m-avatar kole-m-avatar--small kole-m-avatar--circle" role="img" aria-label="王小明 头像" data-assert="avatar-size-small"><span class="kole-m-avatar__text">王</span></span>
      <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="李小红 头像" data-assert="avatar-size-default"><span class="kole-m-avatar__text">李</span></span>
      <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--circle" role="img" aria-label="赵小刚 头像" data-assert="avatar-size-large"><span class="kole-m-avatar__text">赵</span></span>
    </div>
  </section>

  <section class="demo-block" data-demo="shape">
    <p class="demo-label">两种形状(circle 用于人 / square 用于企业、群组)</p>
    <div class="demo-box">
      <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="张 头像" data-assert="avatar-circle"><span class="kole-m-avatar__text">张</span></span>
      <span class="kole-m-avatar kole-m-avatar--square" role="img" aria-label="云启科技 标志" data-assert="avatar-square"><span class="kole-m-avatar__text">云启</span></span>
      <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--square" role="img" aria-label="设计中心 标志"><span class="kole-m-avatar__text">设计</span></span>
    </div>
  </section>

  <section class="demo-block" data-demo="image">
    <p class="demo-label">图片头像(type=image:铺满容器并按形状裁切)</p>
    <div class="demo-box">
      <span class="kole-m-avatar kole-m-avatar--circle" data-assert="avatar-image-circle">
        <img class="kole-m-avatar__image" alt="示例用户 头像"
             src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCI+PHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjMkY1NEVCIi8+PGNpcmNsZSBjeD0iNDAiIGN5PSIzMCIgcj0iMTMiIGZpbGw9IiNGRkZGRkYiLz48cGF0aCBkPSJNMTMgNzRjNC0xNSAxNC0yMiAyNy0yMnMyMyA3IDI3IDIyeiIgZmlsbD0iI0ZGRkZGRiIvPjwvc3ZnPg==">
      </span>
      <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--square" data-assert="avatar-image-square">
        <img class="kole-m-avatar__image" alt="示例企业 标志"
             src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCI+PHJlY3Qgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBmaWxsPSIjMkY1NEVCIi8+PGNpcmNsZSBjeD0iNDAiIGN5PSIzMCIgcj0iMTMiIGZpbGw9IiNGRkZGRkYiLz48cGF0aCBkPSJNMTMgNzRjNC0xNSAxNC0yMiAyNy0yMnMyMyA3IDI3IDIyeiIgZmlsbD0iI0ZGRkZGRiIvPjwvc3ZnPg==">
      </span>
    </div>
  </section>

  <section class="demo-block" data-demo="fallback">
    <p class="demo-label">图片兜底(状态 fallback:图片不可用时退回文字,不出现裂图)</p>
    <div class="demo-box">
      <span class="kole-m-avatar kole-m-avatar--circle is-fallback" role="img" aria-label="头像加载失败,显示 陈" data-assert="avatar-fallback">
        <span class="kole-m-avatar__text">陈</span>
      </span>
      <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="周 头像">
        <span class="kole-m-avatar__text">周</span>
      </span>
      <span class="demo-tag">图片失败 → 文字</span>
    </div>
  </section>

  <section class="demo-block" data-demo="badge">
    <p class="demo-label">带角标(badge 位:挂在线状态或未读数,不改变头像尺寸)</p>
    <div class="demo-box">
      <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="吴 头像,在线" data-assert="avatar-badge-online">
        <span class="kole-m-avatar__text">吴</span>
        <span class="kole-m-avatar__badge" aria-hidden="true"></span>
      </span>
      <span class="kole-m-avatar kole-m-avatar--large kole-m-avatar--square" role="img" aria-label="研发一组 标志,在线">
        <span class="kole-m-avatar__text">研发</span>
        <span class="kole-m-avatar__badge" aria-hidden="true"></span>
      </span>
    </div>
  </section>

  <section class="demo-block" data-demo="disabled">
    <p class="demo-label">禁用(状态 disabled:置灰,用于已离职或已停用的主体)</p>
    <div class="demo-box">
      <span class="kole-m-avatar kole-m-avatar--circle is-disabled" role="img" aria-label="孙 头像(已停用)" data-assert="avatar-disabled" aria-disabled="true">
        <span class="kole-m-avatar__text">孙</span>
      </span>
      <span class="kole-m-avatar kole-m-avatar--circle" role="img" aria-label="钱 头像">
        <span class="kole-m-avatar__text">钱</span>
      </span>
    </div>
  </section>
</div>
<script>
  /* ?demo=<id> → 只显示该演示块(文档站按块预览用;无参数时全部显示,测试与回归走无参数路径) */
  (function () {
    var id = new URLSearchParams(location.search).get('demo');
    if (!id) return;
    var blocks = Array.prototype.slice.call(document.querySelectorAll('.demo-block'));
    var hit = false;
    blocks.forEach(function (b) {
      var on = b.getAttribute('data-demo') === id;
      if (on) hit = true;
      b.hidden = !on;
    });
    if (!hit) { blocks.forEach(function (b) { b.hidden = false; }); return; }
    document.body.classList.add('demo-single');
    blocks.forEach(function (b) {
      var label = b.querySelector('.demo-label');
      if (label && !b.hidden) label.hidden = true;
    });
  })();
</script>
</body>
</html>
frameworks-mobile/Avatar.jsx · React · 54 行
frameworks-mobile/Avatar.jsx
import React, { useState } from 'react';
import './Avatar.css';

/* 头像(移动端)— 规格 §23
   文字头像用 role="img" + aria-label(读屏读「姓名 头像」);图片头像的语义由 <img alt> 承担,
   加载失败只回传一次 error 事件并把内容退回文字(状态 fallback),宿主决定要不要换资源。 */
export default function Avatar({
  size = 'default',
  shape = 'circle',
  type = 'text',
  src = '',
  alt = '',
  disabled = false,
  onError,
  children = null,
  badge = null,
}) {
  const [failed, setFailed] = useState(false);
  const showImage = type === 'image' && !!src && !failed;
  const fallback = type === 'image' && failed;

  const cls =
    'kole-m-avatar' +
    ` kole-m-avatar--${size}` +
    ` kole-m-avatar--${shape}` +
    (fallback ? ' is-fallback' : '') +
    (disabled ? ' is-disabled' : '');

  function handleError(e) {
    setFailed(true);
    if (onError) onError(e);
  }

  return (
    <span
      className={cls}
      role={showImage ? undefined : 'img'}
      aria-label={showImage ? undefined : alt || undefined}
      aria-disabled={disabled ? 'true' : undefined}
    >
      {showImage ? (
        <img className="kole-m-avatar__image" src={src} alt={alt} onError={handleError} />
      ) : (
        <span className="kole-m-avatar__text">{children}</span>
      )}
      {badge ? (
        <span className="kole-m-avatar__badge" aria-hidden="true">
          {badge}
        </span>
      ) : null}
    </span>
  );
}
frameworks-mobile/Avatar.vue2.vue · Vue 2 · 58 行
frameworks-mobile/Avatar.vue2.vue
<template>
  <span
    class="kole-m-avatar"
    :class="avatarClass"
    :role="showImage ? null : 'img'"
    :aria-label="showImage ? null : (alt || null)"
    :aria-disabled="disabled ? 'true' : null"
  >
    <img
      v-if="showImage"
      class="kole-m-avatar__image"
      :src="src"
      :alt="alt"
      @error="onImageError"
    >
    <span v-else class="kole-m-avatar__text"><slot /></span>
    <span v-if="$slots.badge" class="kole-m-avatar__badge" aria-hidden="true"><slot name="badge" /></span>
  </span>
</template>

<script>
export default {
  name: 'KoleMAvatar',
  props: {
    size: { type: String, default: 'default' },
    shape: { type: String, default: 'circle' },
    type: { type: String, default: 'text' },
    src: { type: String, default: '' },
    alt: { type: String, default: '' },
    disabled: { type: Boolean, default: false }
  },
  data: function () {
    return { failed: false };
  },
  computed: {
    showImage: function () {
      return this.type === 'image' && !!this.src && !this.failed;
    },
    avatarClass: function () {
      return [
        'kole-m-avatar--' + this.size,
        'kole-m-avatar--' + this.shape,
        this.type === 'image' && this.failed ? 'is-fallback' : '',
        this.disabled ? 'is-disabled' : ''
      ].filter(Boolean);
    }
  },
  methods: {
    onImageError: function (e) {
      this.failed = true;
      this.$emit('error', e);
    }
  }
};
</script>

<style src="./Avatar.css"></style>
frameworks-mobile/Avatar.vue3.vue · Vue 3 · 52 行
frameworks-mobile/Avatar.vue3.vue
<template>
  <span
    class="kole-m-avatar"
    :class="avatarClass"
    :role="showImage ? null : 'img'"
    :aria-label="showImage ? null : (alt || null)"
    :aria-disabled="disabled ? 'true' : null"
  >
    <img
      v-if="showImage"
      class="kole-m-avatar__image"
      :src="src"
      :alt="alt"
      @error="onImageError"
    >
    <span v-else class="kole-m-avatar__text"><slot /></span>
    <span v-if="$slots.badge" class="kole-m-avatar__badge" aria-hidden="true"><slot name="badge" /></span>
  </span>
</template>

<script setup>
/* 头像(移动端)— 规格 §23:图片加载失败退回文字(状态 fallback),并 emit error 一次 */
import { computed, ref } from 'vue';

const props = defineProps({
  size: { type: String, default: 'default' },
  shape: { type: String, default: 'circle' },
  type: { type: String, default: 'text' },
  src: { type: String, default: '' },
  alt: { type: String, default: '' },
  disabled: { type: Boolean, default: false }
});
const emit = defineEmits(['error']);

const failed = ref(false);
const showImage = computed(() => props.type === 'image' && !!props.src && !failed.value);

const avatarClass = computed(() => [
  `kole-m-avatar--${props.size}`,
  `kole-m-avatar--${props.shape}`,
  props.type === 'image' && failed.value ? 'is-fallback' : '',
  props.disabled ? 'is-disabled' : ''
].filter(Boolean));

function onImageError(e) {
  failed.value = true;
  emit('error', e);
}
</script>

<style src="./Avatar.css"></style>
frameworks-mobile/Avatar.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 131 行
frameworks-mobile/Avatar.uniapp.vue
<template>
  <view
    class="kole-m-avatar"
    :class="avatarClass"
    :role="showImage ? '' : 'img'"
    :aria-label="showImage ? '' : (alt || '')"
    :aria-disabled="disabled ? 'true' : 'false'"
  >
    <image
      v-if="showImage"
      class="kole-m-avatar__image"
      :src="src"
      :alt="alt"
      mode="aspectFill"
      @error="onImageError"
    ></image>
    <text v-else class="kole-m-avatar__text"><slot></slot></text>
    <view v-if="$slots.badge" class="kole-m-avatar__badge" aria-hidden="true">
      <slot name="badge"></slot>
    </view>
  </view>
</template>

<script setup>
/* uni-app 端 · 头像(移动端)— 规格 §23
   跨端差异:图片用 <image mode="aspectFill"> 承担裁切(小程序没有 object-fit 语义),
   加载失败走 @error 回退文字(与 H5 端的 is-fallback 同一类名);
   尺寸用 rpx(88rpx = 375pt 下的 44px,故 40px 头像 = 80rpx)。 */
import { computed, ref } from 'vue';

const props = defineProps({
  size: { type: String, default: 'default' },
  shape: { type: String, default: 'circle' },
  type: { type: String, default: 'text' },
  src: { type: String, default: '' },
  alt: { type: String, default: '' },
  disabled: { type: Boolean, default: false }
});
const emit = defineEmits(['error']);

const failed = ref(false);
const showImage = computed(() => props.type === 'image' && !!props.src && !failed.value);

const avatarClass = computed(() => [
  `kole-m-avatar--${props.size}`,
  `kole-m-avatar--${props.shape}`,
  props.type === 'image' && failed.value ? 'is-fallback' : '',
  props.disabled ? 'is-disabled' : ''
].filter(Boolean));

function onImageError(e) {
  failed.value = true;
  emit('error', e);
}
</script>

<style>
.kole-m-avatar {
  --kole-m-avatar-size: 80rpx;        /* 三档尺寸:small 64rpx / default 80rpx / large 112rpx */
  --kole-m-avatar-radius: 50%;
  --kole-m-avatar-font-size: 28rpx;
  --kole-m-avatar-badge-size: 20rpx;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--kole-m-avatar-size);
  height: var(--kole-m-avatar-size);
  overflow: hidden;
  border-radius: var(--kole-m-avatar-radius);
  background-color: var(--kole-color-brand);
  color: var(--kole-color-text-inverse);
  font-size: var(--kole-m-avatar-font-size);
  line-height: 1;
}

.kole-m-avatar--small {
  --kole-m-avatar-size: 64rpx;
  --kole-m-avatar-font-size: 24rpx;
  --kole-m-avatar-badge-size: 16rpx;
}

.kole-m-avatar--large {
  --kole-m-avatar-size: 112rpx;
  --kole-m-avatar-font-size: 36rpx;
  --kole-m-avatar-badge-size: 24rpx;
}

.kole-m-avatar--circle { --kole-m-avatar-radius: 50%; }
.kole-m-avatar--square { --kole-m-avatar-radius: 12rpx; }

.kole-m-avatar__image {
  display: block;
  width: 100%;
  height: 100%;
}

.kole-m-avatar__text {
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.kole-m-avatar.is-fallback {
  background-color: var(--kole-color-table-header-bg);
  color: var(--kole-color-text-secondary);
}

.kole-m-avatar.is-disabled {
  background-color: var(--kole-color-disabled-bg);
  color: var(--kole-color-text-disabled);
}

.kole-m-avatar__badge {
  position: absolute;
  right: 0;
  bottom: 0;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--kole-m-avatar-badge-size);
  height: var(--kole-m-avatar-badge-size);
  border: 4rpx solid var(--kole-color-card-bg);
  border-radius: 50%;
  background-color: var(--kole-color-success);
}
</style>

测试与回归

断言在真实的 375×640 设备帧里跑(引擎与 PC 侧共用 tests/_runtime.js,触控行为动词来自移动端 tests/mobile/_behaviors.js)。

断言 20 条 · 全部通过 报告 2026-09-20 16:50:33

复现命令
node site/dev-server.js &
REG_BASE=http://127.0.0.1:3311 node tools/run-mobile-regression.mjs   # 全量 5 个组件
npm run verify:mobile-docs                                           # 本页内容完整性 + API 与源码一致性

设计契约

components/mobile-avatar.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "specSection": "23 · 头像 Avatar",
  "confidence": "high",
  "slug": "mobile-avatar",
  "name": "头像 Avatar",
  "semanticTypeCandidates": [
    "avatar",
    "user-image",
    "profile-photo"
  ],
  "variantDimensions": [
    {
      "name": "size",
      "values": [
        "small",
        "default",
        "large"
      ]
    },
    {
      "name": "shape",
      "values": [
        "circle",
        "square"
      ]
    },
    {
      "name": "type",
      "values": [
        "text",
        "image"
      ]
    }
  ],
  "representativeVariants": [
    {
      "size": "default",
      "shape": "circle",
      "type": "text",
      "label": "文字头像(圆形,用户默认形态)"
    },
    {
      "size": "large",
      "shape": "square",
      "type": "image",
      "label": "图片头像(圆角方形,企业 / 群组)"
    },
    {
      "size": "small",
      "shape": "circle",
      "type": "text",
      "label": "小尺寸头像(列表行前缀位)"
    },
    {
      "size": "default",
      "shape": "circle",
      "type": "image",
      "label": "图片加载失败退回文字(状态 fallback)"
    }
  ],
  "anatomy": {
    "avatar": "根元素,正方形圆角容器,尺寸由 size 决定",
    "image": "图片,铺满容器并按形状裁切",
    "text": "文字内容(姓名首字或简称),图片缺失时它就是主体",
    "fallback": "兜底节点,图片加载失败后退回的形状(文字或图标)",
    "badge": "可选右下角角标位,挂在线状态或未读数"
  },
  "structurePatterns": {
    "size": "small(32px)/ default(40px)/ large(56px)",
    "shape": "circle(圆形,用于人)/ square(圆角方形,用于企业或群组)",
    "type": "text(文字头像)/ image(图片头像,失败退文字)"
  },
  "usageHints": [
    "用一张图或一两个字符代表一个主体(用户、企业、群组);移动端列表与详情页里大量出现,必须能单手扫读,因此尺寸只有三档、形状只有两种",
    "图片不可用时必须立刻退回文字,不能出现裂图",
    "头像本身不是按钮;可点时必须由宿主包一层原生 button 或 a,热区不小于 44×44",
    "图片 alt 或根节点 aria-label 必填其一,读屏读「姓名 + 头像」",
    "图片加载失败只触发一次 error 事件,是否替换资源由宿主决定"
  ],
  "doNotInvent": [
    "图片裁剪的 focal point(人脸居中)算法",
    "角标位置随形状(圆 / 方)的微调规则"
  ],
  "unknowns": [
    "文字头像的底色是否按名字散列取多色",
    "姓名超过两个汉字时的截断规则"
  ],
  "interaction": [
    "头像本身不是按钮;可点时必须由宿主包一层原生 button 或 a,热区不小于 44×44",
    "图片加载失败只触发一次 error 事件,是否替换资源由宿主决定"
  ],
  "accessibility": [
    "文字头像根节点 role=\"img\" 并带 aria-label(内容为姓名)",
    "图片头像直接用 ,alt 为空时视为装饰并对读屏隐藏",
    "角标不改变头像的可访问名"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "size",
        "type": "'small' | 'default' | 'large'",
        "default": "'default'",
        "desc": "变体 size:三档尺寸 32 / 40 / 56px,文字随尺寸放大(规格 §23.3)",
        "required": false
      },
      {
        "name": "shape",
        "type": "'circle' | 'square'",
        "default": "'circle'",
        "desc": "变体 shape:circle 用于人,square 用于企业或群组(规格 §23.3)",
        "required": false
      },
      {
        "name": "type",
        "type": "'text' | 'image'",
        "default": "'text'",
        "desc": "变体 type:文字头像 / 图片头像,图片失败退回文字(规格 §23.3)",
        "required": false
      },
      {
        "name": "src",
        "type": "string",
        "default": "''",
        "desc": "图片地址,type=image 时必填(规格 §23.2 image)",
        "required": false
      },
      {
        "name": "alt",
        "type": "string",
        "default": "''",
        "desc": "可访问名:图片头像作为 ,文字头像作为根节点 aria-label(规格 §23.6)",
        "required": false
      },
      {
        "name": "disabled",
        "type": "boolean",
        "default": "false",
        "desc": "状态 disabled:置灰(如成员已离职)(规格 §23.4)",
        "required": false
      }
    ],
    "events": [
      {
        "name": "error",
        "params": "(event)",
        "desc": "图片加载失败时触发一次;是否替换资源由宿主决定(规格 §23.5)"
      }
    ],
    "slots": [
      {
        "name": "default",
        "desc": "文字内容(姓名首字或简称)(规格 §23.2 text)"
      },
      {
        "name": "badge",
        "desc": "右下角角标位,挂在线状态或未读数(规格 §23.2 badge)"
      }
    ]
  },
  "variantClasses": {
    "size": {
      "small": [
        ".kole-m-avatar--small"
      ],
      "default": [],
      "large": [
        ".kole-m-avatar--large"
      ]
    },
    "shape": {
      "circle": [
        ".kole-m-avatar--circle"
      ],
      "square": [
        ".kole-m-avatar--square"
      ]
    },
    "type": {
      "text": [],
      "image": [
        ".kole-m-avatar__image"
      ]
    }
  },
  "demos": [
    {
      "id": "size",
      "group": "01 组件类型",
      "title": "三档尺寸",
      "desc": "small 32 / default 40 / large 56,文字字号随尺寸放大,小尺寸下仍可读。",
      "variant": "size=small|default|large"
    },
    {
      "id": "shape",
      "group": "01 组件类型",
      "title": "两种形状",
      "desc": "circle 用于人(成员、好友),square 用于企业或群组(标志、团队)。",
      "variant": "shape=circle|square"
    },
    {
      "id": "image",
      "group": "01 组件类型",
      "title": "图片头像",
      "desc": "type=image 时图片铺满容器并按形状裁切,语义交给 。",
      "variant": "type=image"
    },
    {
      "id": "fallback",
      "group": "02 组件状态",
      "title": "图片兜底",
      "desc": "状态 fallback:图片不可用时退回文字,底色转中性底,绝不出现裂图。",
      "variant": "状态 fallback"
    },
    {
      "id": "badge",
      "group": "02 组件状态",
      "title": "带角标",
      "desc": "角标位挂在线状态或未读数,绝对定位不改变头像尺寸。",
      "variant": "slots.badge"
    },
    {
      "id": "disabled",
      "group": "02 组件状态",
      "title": "禁用",
      "desc": "状态 disabled:置灰用于已离职或已停用的主体。",
      "variant": "disabled=true"
    }
  ],
  "related": [
    {
      "slug": "mobile-list",
      "why": "头像作为列表行的前缀位时用列表承载整行;只有单张头像时用本组件"
    },
    {
      "slug": "mobile-badge",
      "why": "需要数字角标时用徽标组件包裹头像;只表示在线状态用本组件自带的 badge 位"
    },
    {
      "slug": "mobile-tag",
      "why": "要用文字标注状态或分类时用标签;头像只承载主体身份"
    }
  ]
}