移动端导航排版

排版Typography

把一段文字按信息层级(标题 / 正文 / 辅助 / 次要)成套地表达,并可选单行省略与一键复制

数据展示 规格 37 · 排版 Typography 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-typography.css">

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

演示

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

01 组件类型

四个层级level=title|body|assist|secondary

标题 / 正文 / 辅助 / 次要:字号与字色全部走令牌,层级差异一眼可辨而不靠颜色深浅硬撑。

查看代码(演示页原文 · 23 行)
frameworks-mobile/Typography.html · levels
<section class="demo-block" data-demo="levels">
  <p class="demo-label">四个层级(level=title / body / assist / secondary,字号与字色全部走令牌)</p>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--title" data-assert="typography-title">
      <span class="kole-m-typography__text">待付款订单</span>
    </div>
  </div>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--body" data-assert="typography-body">
      <span class="kole-m-typography__text">正文用于承载主体内容,移动端 16px 比桌面端 14px 更大。</span>
    </div>
  </div>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--assist" data-assert="typography-assist">
      <span class="kole-m-typography__text">辅助说明:补充正文但没有正文重要</span>
    </div>
  </div>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--secondary" data-assert="typography-secondary">
      <span class="kole-m-typography__text">次要信息:2026-09-20 09:12</span>
    </div>
  </div>
</section>
段落正文level=body

默认不省略,长文本按容器宽度换行;行高取 1.5,比桌面端的 1.4 更宽以便手指阅读定位。

查看代码(演示页原文 · 8 行)
frameworks-mobile/Typography.html · paragraph
<section class="demo-block" data-demo="paragraph">
  <p class="demo-label">段落正文(默认不省略,长文本按容器宽度换行)</p>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--body" data-assert="typography-paragraph">
      <span class="kole-m-typography__text">移动端的正文行高取 1.5 而非桌面的 1.4:手指阅读时行距更宽,长段落的回归定位更省力。层级只表达信息次序,不表达重要性判断。</span>
    </div>
  </div>
</section>

02 组件状态

单行省略ellipsis=true

ellipsis=true:超宽截断、省略号在行尾;读屏仍能读到完整文字,被截断的内容不提供展开入口。

查看代码(演示页原文 · 8 行)
frameworks-mobile/Typography.html · ellipsis
<section class="demo-block" data-demo="ellipsis">
  <p class="demo-label">单行省略(ellipsis=true:超宽截断,省略号在行尾;多行省略不在本组件范围)</p>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--body kole-m-typography--ellipsis" data-assert="typography-ellipsis">
      <span class="kole-m-typography__text">杭州市余杭区文一西路 969 号淘宝城 5 号楼 3 层西侧会议室</span>
    </div>
  </div>
</section>
可复制copyable=true

copyable=true:复制按钮热区 44px;点一下标记已复制,组件不读剪贴板,写入由宿主完成。

查看代码(演示页原文 · 18 行)
frameworks-mobile/Typography.html · copyable
<section class="demo-block" data-demo="copyable">
  <p class="demo-label">可复制(copyable=true:点右侧按钮复制,宿主写剪贴板后加 is-copied)</p>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--body kole-m-typography--copyable" id="typo-copy"
         data-assert="typography-copyable">
      <span class="kole-m-typography__text">订单号 20260920-8841</span>
      <button class="kole-m-typography__copy" type="button" aria-label="复制订单号"
              data-behavior="click-sets-attr:#typo-copy|data-copied|true">复制</button>
    </div>
  </div>
  <div class="demo-box">
    <div class="kole-m-typography kole-m-typography--assist kole-m-typography--copyable is-copied"
         data-assert="typography-copied">
      <span class="kole-m-typography__text">已复制态(is-copied:按钮文字与色变作为反馈)</span>
      <button class="kole-m-typography__copy" type="button" aria-label="已复制">已复制</button>
    </div>
  </div>
</section>

API

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

Props

名称类型默认值说明必传
level'title' | 'body' | 'assist' | 'secondary''body'变体 level:文字层级(规格 §37.3)N
ellipsisbooleanfalse变体 ellipsis:单行省略,省略号在行尾(规格 §37.3)N
copyablebooleanfalse变体 copyable:右侧出现复制按钮(规格 §37.3)N
copyTextstring'复制'复制按钮的常态文字,同时作为 aria-label(规格 §37.6)N
copiedTextstring'已复制'copied=true 时复制按钮的文字(规格 §37.4)N
textstring''文字内容;与默认插槽二选一,text 优先(规格 §37.2 text)N
copiedbooleanfalse状态 copied:宿主写剪贴板成功后置 true(规格 §37.4)N

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

事件

名称参数说明
copy—点击复制按钮时触发;真正的剪贴板写入由宿主完成(规格 §37.5)

插槽

名称说明
default文字内容(与 text 二选一;text 优先)

CSS 变量

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

名称默认值说明
--kole-m-typography-gapvar(--kole-space-8)组件内部默认值,可在业务侧覆盖

何时使用

  • 把一段文字按信息层级(标题 / 正文 / 辅助 / 次要)成套地表达,并可选单行省略与一键复制
  • 移动端正文基准 16px(桌面端 14px),层级只分四级:再细分会让差异小于字号可辨阈值
  • 组件本体是静态的:根与文字节点不带指针语义,也不进键盘序列
  • 复制动作由宿主完成 —— 组件不读系统剪贴板,只回传 copy 事件
  • 省略形态下读屏仍能读到完整文字(text-overflow 只截视觉,不改可访问名)

交互与触控

  • 组件本体是静态的:根与文字节点不带指针语义,也不进键盘序列
  • 唯一的交互元素是复制按钮:热区 44×44px,一次轻点触发 copy 事件
  • 复制动作由宿主完成:组件不读系统剪贴板,组件只回传事件并在宿主置 copied=true 后渲染反馈
  • ellipsis=true 时只做单行省略:被截断的内容不提供展开入口

无障碍

  • 根是普通容器,文字由读屏按文档流朗读;层级只改视觉,不改语义标签
  • 省略形态下读屏仍能读到完整文字
  • 复制按钮是原生 button 且带 aria-label,名称随状态在「复制」与「已复制」之间切换

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

组件何时用它而不是本组件
链接Link文字需要可点击跳转时用链接,排版只负责层级与省略、不承担点击语义
标签Tag行内状态标记用标签,不要用排版加粗加色来表达状态
单元格Cell带副标题的两行列表信息用单元格(标题 + 说明一次成组),单个文字层级才用排版

规格未定 / 禁止发明

类别条目
禁止发明富文本与 Markdown 渲染(换行、加粗、链接、代码块一律由宿主负责)
禁止发明多行省略(line-clamp)与「展开全文」交互
禁止发明字号缩放 / 用户字号偏好档位
禁止发明复制失败的兜底提示(宿主自行组合轻提示)
规格未定secondary 层级是否应使用 11px(当前取 --kole-m-font-size-caption)而不是 12px
规格未定复制按钮在「已复制」态停留多久(当前不自动回退,由宿主控制)
规格未定省略态是否需要在长按气泡里显示全文

结构(anatomy)

字段说明
typography根元素,横向承载「文字 + 可选复制按钮」
text文字本体,层级、省略、换行都在它身上生效;默认插槽与 text prop 二者取一(text 优先)
copy复制按钮(原生 button),仅 copyable=true 时出现;热区 44px,负外边距吸收不撑高行

变体维度与类名映射

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

维度取值对应类名 / 变量
leveltitle / body / assist / secondary
title .kole-m-typography--title
body .kole-m-typography--body
assist .kole-m-typography--assist
secondary .kole-m-typography--secondary
ellipsisfalse / true
false (由数据驱动,无专属类)
true .kole-m-typography--ellipsis
copyablefalse / true
false (由数据驱动,无专属类)
true .kole-m-typography--copyable

代表变体

变体标签
level=title标题(17px / 600)
level=body正文(16px / 400,移动端基准)
level=assist辅助说明(14px / 次级色)
level=secondary次要信息(11px / 三级色)

用到的令牌

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

--kole-m-font-size-body --kole-m-font-size-caption --kole-m-font-size-label --kole-m-font-size-title --kole-m-touch-target --kole-color-brand --kole-color-brand-bg --kole-color-focus-ring --kole-color-success --kole-color-text-body --kole-color-text-secondary --kole-color-text-tertiary --kole-color-text-title --kole-font-family --kole-radius-base --kole-space-8 --kole-m-typography-gap

6 端源码

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

frameworks-mobile/Typography.css · 纯样式(CSS) · 90 行
frameworks-mobile/Typography.css
/* Kole UI Mobile · Typography 样式 — 对齐移动端规格 §37
   文字层级:标题 / 正文 / 辅助 / 次要;字号与字色全部走令牌,本文件不写死字号。
   可省略(ellipsis)只在单行生效;可复制(copyable)右侧给一个原生 button —— 复制动作本身
   由宿主负责(本组件不读剪贴板),宿主完成后给根元素加 is-copied 作为反馈。
   静态组件:根与文字节点不带 cursor:pointer,唯一的可点元素是复制按钮(原生 button)。 */

.kole-m-typography {
  --kole-m-typography-gap: var(--kole-space-8);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--kole-m-typography-gap);
  color: var(--kole-color-text-body);
  font-family: var(--kole-font-family);
  font-size: var(--kole-m-font-size-body);
  line-height: 1.5;
}

.kole-m-typography__text {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
}

/* 变体 level:四个层级(标题 / 正文 / 辅助 / 次要) */
.kole-m-typography--title {
  font-size: var(--kole-m-font-size-title);
  font-weight: 600;
  line-height: 1.4;
  color: var(--kole-color-text-title);
}

.kole-m-typography--body {
  font-size: var(--kole-m-font-size-body);
  color: var(--kole-color-text-body);
}

.kole-m-typography--assist {
  font-size: var(--kole-m-font-size-label);
  color: var(--kole-color-text-secondary);
}

.kole-m-typography--secondary {
  font-size: var(--kole-m-font-size-caption);
  color: var(--kole-color-text-tertiary);
}

/* 变体 ellipsis=true:单行省略(多行省略不在本组件范围内) */
.kole-m-typography--ellipsis .kole-m-typography__text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* 变体 copyable=true:右侧出现复制按钮(热区 44px,负外边距吸收,不撑高行) */
.kole-m-typography--copyable .kole-m-typography__copy {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: var(--kole-m-touch-target);
  height: var(--kole-m-touch-target);
  margin-inline-end: calc(-1 * var(--kole-space-8));
  padding: 0;
  border: 0;
  border-radius: var(--kole-radius-base);
  background: none;
  color: var(--kole-color-brand);
  font-family: inherit;
  font-size: var(--kole-m-font-size-label);
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

.kole-m-typography--copyable .kole-m-typography__copy:active {
  background: var(--kole-color-brand-bg);
}

.kole-m-typography--copyable .kole-m-typography__copy:focus-visible {
  outline: 2px solid var(--kole-color-focus-ring);
  outline-offset: -2px;
}

/* 状态 is-copied:宿主复制成功后加的反馈(色变,不改文字宽度以免布局跳动) */
.kole-m-typography.is-copied .kole-m-typography__copy {
  color: var(--kole-color-success);
}
frameworks-mobile/Typography.html · H5 原生(无框架) · 123 行
frameworks-mobile/Typography.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 · Typography(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Typography.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 { padding: var(--kole-space-12) var(--kole-m-gutter); background: var(--kole-color-card-bg);
    border-block: 1px solid var(--kole-color-border); }
  .demo-box + .demo-box { border-top: 0; }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="levels">
    <p class="demo-label">四个层级(level=title / body / assist / secondary,字号与字色全部走令牌)</p>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--title" data-assert="typography-title">
        <span class="kole-m-typography__text">待付款订单</span>
      </div>
    </div>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--body" data-assert="typography-body">
        <span class="kole-m-typography__text">正文用于承载主体内容,移动端 16px 比桌面端 14px 更大。</span>
      </div>
    </div>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--assist" data-assert="typography-assist">
        <span class="kole-m-typography__text">辅助说明:补充正文但没有正文重要</span>
      </div>
    </div>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--secondary" data-assert="typography-secondary">
        <span class="kole-m-typography__text">次要信息:2026-09-20 09:12</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="paragraph">
    <p class="demo-label">段落正文(默认不省略,长文本按容器宽度换行)</p>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--body" data-assert="typography-paragraph">
        <span class="kole-m-typography__text">移动端的正文行高取 1.5 而非桌面的 1.4:手指阅读时行距更宽,长段落的回归定位更省力。层级只表达信息次序,不表达重要性判断。</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="ellipsis">
    <p class="demo-label">单行省略(ellipsis=true:超宽截断,省略号在行尾;多行省略不在本组件范围)</p>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--body kole-m-typography--ellipsis" data-assert="typography-ellipsis">
        <span class="kole-m-typography__text">杭州市余杭区文一西路 969 号淘宝城 5 号楼 3 层西侧会议室</span>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="copyable">
    <p class="demo-label">可复制(copyable=true:点右侧按钮复制,宿主写剪贴板后加 is-copied)</p>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--body kole-m-typography--copyable" id="typo-copy"
           data-assert="typography-copyable">
        <span class="kole-m-typography__text">订单号 20260920-8841</span>
        <button class="kole-m-typography__copy" type="button" aria-label="复制订单号"
                data-behavior="click-sets-attr:#typo-copy|data-copied|true">复制</button>
      </div>
    </div>
    <div class="demo-box">
      <div class="kole-m-typography kole-m-typography--assist kole-m-typography--copyable is-copied"
           data-assert="typography-copied">
        <span class="kole-m-typography__text">已复制态(is-copied:按钮文字与色变作为反馈)</span>
        <button class="kole-m-typography__copy" type="button" aria-label="已复制">已复制</button>
      </div>
    </div>
  </section>
</div>
<script>
  /* 演示页交互:点复制按钮 → 标记已复制并切换反馈态。
     真实业务里宿主在这里写剪贴板;演示页只做状态反馈,不读系统剪贴板。 */
  (function () {
    var box = document.getElementById('typo-copy');
    if (!box) return;
    var btn = box.querySelector('.kole-m-typography__copy');
    if (!btn) return;
    btn.addEventListener('click', function () {
      box.classList.add('is-copied');
      box.setAttribute('data-copied', 'true');
      btn.textContent = '已复制';
      btn.setAttribute('aria-label', '已复制');
    });
  })();
</script>
<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');
    var demoBox = document.querySelector('.demo');
    if (demoBox) demoBox.style.minHeight = 'auto';
    blocks.forEach(function (b) {
      var label = b.querySelector('.demo-label');
      if (label && !b.hidden) label.hidden = true;
    });
  })();
</script>
</body>
</html>
frameworks-mobile/Typography.jsx · React · 45 行
frameworks-mobile/Typography.jsx
import React from 'react';
import './Typography.css';

/* 排版(移动端)— 规格 §37
   静态文字组件:层级(title / body / assist / secondary)、单行省略、可复制。
   复制动作由宿主完成(onCopy 回调里写剪贴板),组件只渲染按钮与反馈位 is-copied。
   根与文字节点不带指针语义,唯一可点元素是原生 button。 */
export default function Typography({
  level = 'body',
  ellipsis = false,
  copyable = false,
  copyText = '复制',
  copiedText = '已复制',
  text = '',
  copied = false,
  onCopy,
  children = null,
}) {
  const content = text || children;
  const cls =
    'kole-m-typography' +
    ` kole-m-typography--${level}` +
    (ellipsis ? ' kole-m-typography--ellipsis' : '') +
    (copyable ? ' kole-m-typography--copyable' : '') +
    (copied ? ' is-copied' : '');

  return (
    <div className={cls}>
      <span className="kole-m-typography__text">{content}</span>
      {copyable ? (
        <button
          className="kole-m-typography__copy"
          type="button"
          aria-label={copied ? copiedText : copyText}
          onClick={() => {
            if (onCopy) onCopy();
          }}
        >
          {copied ? copiedText : copyText}
        </button>
      ) : null}
    </div>
  );
}
frameworks-mobile/Typography.vue2.vue · Vue 2 · 40 行
frameworks-mobile/Typography.vue2.vue
<template>
  <div class="kole-m-typography" :class="typoClass">
    <span class="kole-m-typography__text"><slot>{{ text }}</slot></span>
    <button
      v-if="copyable"
      class="kole-m-typography__copy"
      type="button"
      :aria-label="copied ? copiedText : copyText"
      @click="$emit('copy')"
    >{{ copied ? copiedText : copyText }}</button>
  </div>
</template>

<script>
export default {
  name: 'KoleMTypography',
  props: {
    level: { type: String, default: 'body' },
    ellipsis: { type: Boolean, default: false },
    copyable: { type: Boolean, default: false },
    copyText: { type: String, default: '复制' },
    copiedText: { type: String, default: '已复制' },
    text: { type: String, default: '' },
    copied: { type: Boolean, default: false }
  },
  computed: {
    typoClass: function () {
      return [
        'kole-m-typography--' + this.level,
        this.ellipsis ? 'kole-m-typography--ellipsis' : '',
        this.copyable ? 'kole-m-typography--copyable' : '',
        this.copied ? 'is-copied' : ''
      ].filter(Boolean);
    }
  }
};
</script>

<style src="./Typography.css"></style>
frameworks-mobile/Typography.vue3.vue · Vue 3 · 48 行
frameworks-mobile/Typography.vue3.vue
<template>
  <div class="kole-m-typography" :class="typoClass">
    <span class="kole-m-typography__text">{{ content }}</span>
    <button
      v-if="copyable"
      class="kole-m-typography__copy"
      type="button"
      :aria-label="copied ? copiedText : copyText"
      @click="onCopyClick"
    >{{ copied ? copiedText : copyText }}</button>
  </div>
</template>

<script setup>
import { computed, useSlots } from 'vue';

const props = defineProps({
  level: { type: String, default: 'body' },
  ellipsis: { type: Boolean, default: false },
  copyable: { type: Boolean, default: false },
  copyText: { type: String, default: '复制' },
  copiedText: { type: String, default: '已复制' },
  text: { type: String, default: '' },
  copied: { type: Boolean, default: false }
});
const emit = defineEmits(['copy']);

const slots = useSlots();
const content = computed(() => {
  if (props.text) return props.text;
  const nodes = slots.default ? slots.default() : [];
  return nodes.map((n) => (typeof n.children === 'string' ? n.children : '')).join('');
});

const typoClass = computed(() => [
  `kole-m-typography--${props.level}`,
  props.ellipsis ? 'kole-m-typography--ellipsis' : '',
  props.copyable ? 'kole-m-typography--copyable' : '',
  props.copied ? 'is-copied' : ''
].filter(Boolean));

function onCopyClick() {
  emit('copy');
}
</script>

<style src="./Typography.css"></style>
frameworks-mobile/Typography.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 119 行
frameworks-mobile/Typography.uniapp.vue
<template>
  <view class="kole-m-typography" :class="typoClass">
    <text class="kole-m-typography__text">{{ content }}</text>
    <view
      v-if="copyable"
      class="kole-m-typography__copy"
      role="button"
      :aria-label="copied ? copiedText : copyText"
      @tap="onCopyTap"
    >
      <text>{{ copied ? copiedText : copyText }}</text>
    </view>
  </view>
</template>

<script setup>
/* uni-app 端 · 排版(移动端)— 规格 §37
   跨端差异:用 view / text;复制按钮用 view + role="button" + @tap;
   尺寸用 rpx(2rpx ≈ 1px),44px 触控最小边长写成 88rpx。
   复制动作由宿主完成(本端不读剪贴板 API),组件只回传 copy 事件。 */
import { computed } from 'vue';

const props = defineProps({
  level: { type: String, default: 'body' },
  ellipsis: { type: Boolean, default: false },
  copyable: { type: Boolean, default: false },
  copyText: { type: String, default: '复制' },
  copiedText: { type: String, default: '已复制' },
  text: { type: String, default: '' },
  copied: { type: Boolean, default: false }
});
const emit = defineEmits(['copy']);

const typoClass = computed(() => [
  `kole-m-typography--${props.level}`,
  props.ellipsis ? 'kole-m-typography--ellipsis' : '',
  props.copyable ? 'kole-m-typography--copyable' : '',
  props.copied ? 'is-copied' : ''
].filter(Boolean));

const content = computed(() => props.text);

function onCopyTap() {
  emit('copy');
}
</script>

<style>
.kole-m-typography {
  --kole-m-typography-gap: 16rpx;
  --kole-m-touch-target: 88rpx;
  --kole-m-font-size-body: 32rpx;
  --kole-m-font-size-label: 28rpx;
  --kole-m-font-size-title: 34rpx;
  --kole-m-font-size-caption: 22rpx;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--kole-m-typography-gap);
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-body);
  line-height: 1.5;
}

.kole-m-typography__text {
  flex: 1;
  min-width: 0;
}

.kole-m-typography--title {
  font-size: var(--kole-m-font-size-title);
  font-weight: 600;
  line-height: 1.4;
  color: var(--kole-color-text-title);
}

.kole-m-typography--body {
  font-size: var(--kole-m-font-size-body);
  color: var(--kole-color-text-body);
}

.kole-m-typography--assist {
  font-size: var(--kole-m-font-size-label);
  color: var(--kole-color-text-secondary);
}

.kole-m-typography--secondary {
  font-size: var(--kole-m-font-size-caption);
  color: var(--kole-color-text-tertiary);
}

.kole-m-typography--ellipsis .kole-m-typography__text {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.kole-m-typography__copy {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-width: var(--kole-m-touch-target);
  height: var(--kole-m-touch-target);
  margin-right: -16rpx;
  border-radius: 8rpx;
  color: var(--kole-color-brand);
  font-size: var(--kole-m-font-size-label);
}

.kole-m-typography--copyable .kole-m-typography__copy {
  color: var(--kole-color-brand);
}

.kole-m-typography.is-copied .kole-m-typography__copy {
  color: var(--kole-color-success);
}
</style>

测试与回归

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

断言 19 条 · 全部通过 报告 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-typography.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "specSection": "37 · 排版 Typography",
  "confidence": "high",
  "slug": "mobile-typography",
  "name": "排版 Typography",
  "semanticTypeCandidates": [
    "typography",
    "text",
    "paragraph"
  ],
  "variantDimensions": [
    {
      "name": "level",
      "values": [
        "title",
        "body",
        "assist",
        "secondary"
      ]
    },
    {
      "name": "ellipsis",
      "values": [
        "false",
        "true"
      ]
    },
    {
      "name": "copyable",
      "values": [
        "false",
        "true"
      ]
    }
  ],
  "representativeVariants": [
    {
      "level": "title",
      "label": "标题(17px / 600)"
    },
    {
      "level": "body",
      "label": "正文(16px / 400,移动端基准)"
    },
    {
      "level": "assist",
      "label": "辅助说明(14px / 次级色)"
    },
    {
      "level": "secondary",
      "label": "次要信息(11px / 三级色)"
    }
  ],
  "anatomy": {
    "typography": "根元素,横向承载「文字 + 可选复制按钮」",
    "text": "文字本体,层级、省略、换行都在它身上生效;默认插槽与 text prop 二者取一(text 优先)",
    "copy": "复制按钮(原生 button),仅 copyable=true 时出现;热区 44px,负外边距吸收不撑高行"
  },
  "structurePatterns": {
    "level": "title / body / assist / secondary(字号与字色全部走令牌)",
    "ellipsis": "false 换行 / true 单行省略(省略号在行尾)",
    "copyable": "false 纯展示 / true 右侧出现复制按钮"
  },
  "usageHints": [
    "把一段文字按信息层级(标题 / 正文 / 辅助 / 次要)成套地表达,并可选单行省略与一键复制",
    "移动端正文基准 16px(桌面端 14px),层级只分四级:再细分会让差异小于字号可辨阈值",
    "组件本体是静态的:根与文字节点不带指针语义,也不进键盘序列",
    "复制动作由宿主完成 —— 组件不读系统剪贴板,只回传 copy 事件",
    "省略形态下读屏仍能读到完整文字(text-overflow 只截视觉,不改可访问名)"
  ],
  "doNotInvent": [
    "富文本与 Markdown 渲染(换行、加粗、链接、代码块一律由宿主负责)",
    "多行省略(line-clamp)与「展开全文」交互",
    "字号缩放 / 用户字号偏好档位",
    "复制失败的兜底提示(宿主自行组合轻提示)"
  ],
  "unknowns": [
    "secondary 层级是否应使用 11px(当前取 --kole-m-font-size-caption)而不是 12px",
    "复制按钮在「已复制」态停留多久(当前不自动回退,由宿主控制)",
    "省略态是否需要在长按气泡里显示全文"
  ],
  "interaction": [
    "组件本体是静态的:根与文字节点不带指针语义,也不进键盘序列",
    "唯一的交互元素是复制按钮:热区 44×44px,一次轻点触发 copy 事件",
    "复制动作由宿主完成:组件不读系统剪贴板,组件只回传事件并在宿主置 copied=true 后渲染反馈",
    "ellipsis=true 时只做单行省略:被截断的内容不提供展开入口"
  ],
  "accessibility": [
    "根是普通容器,文字由读屏按文档流朗读;层级只改视觉,不改语义标签",
    "省略形态下读屏仍能读到完整文字",
    "复制按钮是原生 button 且带 aria-label,名称随状态在「复制」与「已复制」之间切换"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "level",
        "type": "'title' | 'body' | 'assist' | 'secondary'",
        "default": "'body'",
        "desc": "变体 level:文字层级(规格 §37.3)",
        "required": false
      },
      {
        "name": "ellipsis",
        "type": "boolean",
        "default": "false",
        "desc": "变体 ellipsis:单行省略,省略号在行尾(规格 §37.3)",
        "required": false
      },
      {
        "name": "copyable",
        "type": "boolean",
        "default": "false",
        "desc": "变体 copyable:右侧出现复制按钮(规格 §37.3)",
        "required": false
      },
      {
        "name": "copyText",
        "type": "string",
        "default": "'复制'",
        "desc": "复制按钮的常态文字,同时作为 aria-label(规格 §37.6)",
        "required": false
      },
      {
        "name": "copiedText",
        "type": "string",
        "default": "'已复制'",
        "desc": "copied=true 时复制按钮的文字(规格 §37.4)",
        "required": false
      },
      {
        "name": "text",
        "type": "string",
        "default": "''",
        "desc": "文字内容;与默认插槽二选一,text 优先(规格 §37.2 text)",
        "required": false
      },
      {
        "name": "copied",
        "type": "boolean",
        "default": "false",
        "desc": "状态 copied:宿主写剪贴板成功后置 true(规格 §37.4)",
        "required": false
      }
    ],
    "events": [
      {
        "name": "copy",
        "params": "—",
        "desc": "点击复制按钮时触发;真正的剪贴板写入由宿主完成(规格 §37.5)"
      }
    ],
    "slots": [
      {
        "name": "default",
        "desc": "文字内容(与 text 二选一;text 优先)"
      }
    ]
  },
  "variantClasses": {
    "level": {
      "title": [
        ".kole-m-typography--title"
      ],
      "body": [
        ".kole-m-typography--body"
      ],
      "assist": [
        ".kole-m-typography--assist"
      ],
      "secondary": [
        ".kole-m-typography--secondary"
      ]
    },
    "ellipsis": {
      "false": [],
      "true": [
        ".kole-m-typography--ellipsis"
      ]
    },
    "copyable": {
      "false": [],
      "true": [
        ".kole-m-typography--copyable"
      ]
    }
  },
  "demos": [
    {
      "id": "levels",
      "group": "01 组件类型",
      "title": "四个层级",
      "desc": "标题 / 正文 / 辅助 / 次要:字号与字色全部走令牌,层级差异一眼可辨而不靠颜色深浅硬撑。",
      "variant": "level=title|body|assist|secondary"
    },
    {
      "id": "paragraph",
      "group": "01 组件类型",
      "title": "段落正文",
      "desc": "默认不省略,长文本按容器宽度换行;行高取 1.5,比桌面端的 1.4 更宽以便手指阅读定位。",
      "variant": "level=body"
    },
    {
      "id": "ellipsis",
      "group": "02 组件状态",
      "title": "单行省略",
      "desc": "ellipsis=true:超宽截断、省略号在行尾;读屏仍能读到完整文字,被截断的内容不提供展开入口。",
      "variant": "ellipsis=true"
    },
    {
      "id": "copyable",
      "group": "02 组件状态",
      "title": "可复制",
      "desc": "copyable=true:复制按钮热区 44px;点一下标记已复制,组件不读剪贴板,写入由宿主完成。",
      "variant": "copyable=true"
    }
  ],
  "related": [
    {
      "slug": "mobile-link",
      "why": "文字需要可点击跳转时用链接,排版只负责层级与省略、不承担点击语义"
    },
    {
      "slug": "mobile-tag",
      "why": "行内状态标记用标签,不要用排版加粗加色来表达状态"
    },
    {
      "slug": "cell",
      "why": "带副标题的两行列表信息用单元格(标题 + 说明一次成组),单个文字层级才用排版"
    }
  ]
}