移动端导航布局

布局Layout

把一行内容按比例切成若干列(等分或按 12 栅格取值)

通用 规格 20 · 布局 Layout 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-layout.css">

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

演示

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

01 组件类型

两等分columns=2 / gutter=8

左右对照型信息(待付款 / 待发货)用两等分,列宽自动均分不手算。

查看代码(演示页原文 · 9 行)
frameworks-mobile/Layout.html · cols-2
<section class="demo-block" data-demo="cols-2">
  <p class="demo-label">两等分(columns=2:左右两栏的对照型信息)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--cols-2" data-assert="layout-cols-2">
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">待付款 3</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">待发货 1</div></div>
    </div>
  </div>
</section>
三等分columns=3 / gutter=16

金额、数量、优惠这类短字段用三等分;列内文字过长会换行而不是撑破列宽。

查看代码(演示页原文 · 10 行)
frameworks-mobile/Layout.html · cols-3
<section class="demo-block" data-demo="cols-3">
  <p class="demo-label">三等分(columns=3:金额、数量、状态这类短字段)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--cols-3" data-assert="layout-cols-3">
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">合计 ¥128</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">共 2 件</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">已优惠 ¥20</div></div>
    </div>
  </div>
</section>
四等分columns=4 / gutter=8

指标卡与图标入口最常用的档位;窄屏下保持四列,不折行。

查看代码(演示页原文 · 11 行)
frameworks-mobile/Layout.html · cols-4
<section class="demo-block" data-demo="cols-4">
  <p class="demo-label">四等分(columns=4:指标卡 / 图标入口;窄屏不折行)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--cols-4 kole-m-layout--gutter-8" data-assert="layout-cols-4">
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">访问</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">下单</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">支付</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">完成</div></div>
    </div>
  </div>
</section>

02 组件状态

列间距gutter=0 | 24

0 无缝只用于靠分割线或底色分栏的场景;常规内容用 8 或 16。

查看代码(演示页原文 · 15 行)
frameworks-mobile/Layout.html · gutter
<section class="demo-block" data-demo="gutter">
  <p class="demo-label">列间距(gutter=0 无缝 / 8 / 16 / 24;无缝只用于靠分割线分栏的场景)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--cols-3 kole-m-layout--gutter-0" data-assert="layout-gutter" style="margin-bottom: var(--kole-space-12)">
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">0</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">0</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">0</div></div>
    </div>
    <div class="kole-m-layout kole-m-layout--cols-3 kole-m-layout--gutter-24">
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">24</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">24</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">24</div></div>
    </div>
  </div>
</section>
不等宽col--8 + col--4

主次分栏用列级 span 覆盖行级等分:主栏 8/12,次栏 4/12。

查看代码(演示页原文 · 9 行)
frameworks-mobile/Layout.html · span
<section class="demo-block" data-demo="span">
  <p class="demo-label">不等宽(列级 col--8 / col--4:主次分栏,主栏 2/3)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--gutter-16" data-assert="layout-span">
      <div class="kole-m-layout__col kole-m-layout__col--8"><div class="kole-m-layout__panel">订单详情(主栏)</div></div>
      <div class="kole-m-layout__col kole-m-layout__col--4"><div class="kole-m-layout__panel">操作</div></div>
    </div>
  </div>
</section>
交叉轴对齐align=center

列高不同时用 align=center 让内容垂直居中,默认 start 顶部对齐。

查看代码(演示页原文 · 9 行)
frameworks-mobile/Layout.html · align
<section class="demo-block" data-demo="align">
  <p class="demo-label">交叉轴对齐(align=center:列高不同时垂直居中)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--cols-2 kole-m-layout--align-center" data-assert="layout-align">
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">一行</div></div>
      <div class="kole-m-layout__col"><div class="kole-m-layout__panel">两行<br>内容</div></div>
    </div>
  </div>
</section>
不换行wrap=false

wrap=false 时列横向滚动,纵向页面滚动不受影响(看板式横向列)。

查看代码(演示页原文 · 10 行)
frameworks-mobile/Layout.html · nowrap
<section class="demo-block" data-demo="nowrap">
  <p class="demo-label">不换行(wrap=false:横向滚动,看板式横向列)</p>
  <div class="demo-box">
    <div class="kole-m-layout kole-m-layout--gutter-16 is-nowrap" data-assert="layout-nowrap">
      <div class="kole-m-layout__col" style="flex-basis: 140px"><div class="kole-m-layout__panel">第 1 列</div></div>
      <div class="kole-m-layout__col" style="flex-basis: 140px"><div class="kole-m-layout__panel">第 2 列</div></div>
      <div class="kole-m-layout__col" style="flex-basis: 140px"><div class="kole-m-layout__panel">第 3 列</div></div>
    </div>
  </div>
</section>

API

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

Props

名称类型默认值说明必传
gutter0 | 8 | 16 | 248变体 gutter:列间距四档,由 CSS 变量承载(规格 §20.3)N
columns0 | 2 | 3 | 40变体 columns:行级等分列数,0 表示不启用等分(规格 §20.3)N
align'start' | 'center' | 'end' | 'stretch''start'变体 align:交叉轴对齐(规格 §20.3)N
wrapbooleantrue变体 wrap:false 时不换行、横向滚动(规格 §20.3)N

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

事件

名称参数说明

插槽

名称说明
default列(.kole-m-layout__col)或任意业务内容(规格 §20.2)

CSS 变量

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

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

何时使用

  • 把一行内容按比例切成若干列(等分或按 12 栅格取值)
  • 布局容器不绑点击,不设 cursor: pointer,热区始终由列内的业务元素提供
  • 列内若是可点区域,其热区不小于 44×44(见 §19.5 与 §6.5)
  • gutter 由 CSS 变量承载,同一行内所有列的宽度计算共用该变量,避免手工算宽
  • 列宽使用 flex-basis: calc(...),窄屏下不产生横向溢出(列内文本默认 min-width: 0)

交互与触控

  • 布局容器不绑点击,不设 cursor: pointer,热区始终由列内的业务元素提供
  • 列内若是可点区域,其热区不小于 44×44(见 §19.5 与 §6.5)
  • gutter 由 CSS 变量承载,同一行内所有列的宽度计算共用该变量,避免手工算宽
  • wrap=false 时容器横向滚动,纵向页面滚动不受影响

无障碍

  • 布局是纯视觉分组:不添加 role、不添加 aria-*
  • 不要为了布局把语义节点(列表 / 按钮)拆到不相邻的列里,否则读屏顺序会错乱
  • 视觉顺序必须与 DOM 顺序一致(不用 order 重排)

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

组件何时用它而不是本组件
宫格Grid等分的图标 / 文字入口用宫格(自带图标与文字样式);只是切分版面才用布局
分割线Divider列之间要靠线分栏时用分割线;布局只管间距与宽度
单元格Cell一行一个语义项(可点整行)用单元格;一行要放多个并列块才用布局

规格未定 / 禁止发明

类别条目
禁止发明响应式断点(移动端只有一档列数,不为大屏定义断点)
禁止发明列的排序 / 拖拽
禁止发明栅格嵌套的层级规则
规格未定列内推荐的最大列数(本实现提供 2 / 3 / 4 三档)
规格未定列高等分(stretch 之外的等高策略)

结构(anatomy)

字段说明
layout行容器,display: flex + 换行,承载列间距变量
col列,默认占满一行(flex-basis: 100%),由行级等分或列级 span 类决定实际宽度
panel列内容的可选包壳,提供内边距与最小高度,业务也可直接放自己的卡片
gutter列间距,四档(0 / 8 / 16 / 24),由行级类写到 CSS 变量上
span列级权重类(12 栅格取值),可覆盖行级等分

变体维度与类名映射

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

维度取值对应类名 / 变量
gutter0 / 8 / 16 / 24
0 .kole-m-layout--gutter-0
8 .kole-m-layout--gutter-8 --kole-m-layout-gutter
16 .kole-m-layout--gutter-16
24 .kole-m-layout--gutter-24
columns0 / 2 / 3 / 4
0 (由数据驱动,无专属类)
2 .kole-m-layout--cols-2
3 .kole-m-layout--cols-3
4 .kole-m-layout--cols-4
alignstart / center / end / stretch
start (由数据驱动,无专属类)
center .kole-m-layout--align-center
end .kole-m-layout--align-end
stretch .kole-m-layout--align-stretch
wraptrue / false
true (由数据驱动,无专属类)
false .is-nowrap

代表变体

变体标签
columns=2 · gutter=8两等分(左右对照)
columns=3 · gutter=16三等分(短字段)
columns=4 · gutter=8四等分(指标卡 / 入口)
columns=0 · gutter=16不等宽(列级 span 主次分栏)

用到的令牌

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

--kole-m-font-size-label --kole-m-touch-target --kole-color-page-bg --kole-color-table-header-bg --kole-color-text-body --kole-color-text-secondary --kole-radius-medium --kole-space-12 --kole-space-16 --kole-space-24 --kole-space-8 --kole-m-layout-gutter

6 端源码

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

frameworks-mobile/Layout.css · 纯样式(CSS) · 76 行
frameworks-mobile/Layout.css
/* Kole UI Mobile · Layout 样式 — 对齐 .design_library/kole-ui-mobile/spec/移动端规格.md §20
   栅格:一行(row)里放若干列(col)。移动端绝大多数场景是「等分」,所以 columns 是行级开关
   (2 / 3 / 4 等分);需要不等宽时用列上的 span 类覆盖(span 规则写成两段选择器,
   保证它能压过行级等分规则 —— 两者同源,顺序在后者胜出)。
   --kole-m-layout-gutter 是组件级变量,业务侧可直接覆盖(不必传 gutter)。 */

.kole-m-layout {
  --kole-m-layout-gutter: var(--kole-space-8);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--kole-m-layout-gutter);
  box-sizing: border-box;
  width: 100%;
}

/* 变体 gutter:列间距四档(0 = 无缝,用分割线或底色分栏时才用) */
.kole-m-layout--gutter-0 { --kole-m-layout-gutter: 0px; }
.kole-m-layout--gutter-8 { --kole-m-layout-gutter: var(--kole-space-8); }
.kole-m-layout--gutter-16 { --kole-m-layout-gutter: var(--kole-space-16); }
.kole-m-layout--gutter-24 { --kole-m-layout-gutter: var(--kole-space-24); }

/* 变体 align:交叉轴对齐(列高不同时观察得到差别) */
.kole-m-layout--align-start { align-items: flex-start; }
.kole-m-layout--align-center { align-items: center; }
.kole-m-layout--align-end { align-items: flex-end; }
.kole-m-layout--align-stretch { align-items: stretch; }

/* 变体 columns:行内等分(0 = 不禁用等分,列宽由 span 或内容决定) */
.kole-m-layout--cols-2 > .kole-m-layout__col { flex-basis: calc((100% - var(--kole-m-layout-gutter)) / 2); }
.kole-m-layout--cols-3 > .kole-m-layout__col { flex-basis: calc((100% - 2 * var(--kole-m-layout-gutter)) / 3); }
.kole-m-layout--cols-4 > .kole-m-layout__col { flex-basis: calc((100% - 3 * var(--kole-m-layout-gutter)) / 4); }

/* 变体 wrap=false:不换行,横向滚动(看板式横向列) */
.kole-m-layout.is-nowrap { flex-wrap: nowrap; overflow-x: auto; }

.kole-m-layout__col {
  box-sizing: border-box;
  min-width: 0;
  flex: 0 0 auto;
  flex-basis: 100%;
}

/* 列宽(12 栅格):两段选择器,确保能覆盖行级等分 */
.kole-m-layout .kole-m-layout__col--12 { flex-basis: 100%; }
.kole-m-layout .kole-m-layout__col--8 { flex-basis: calc((100% - 2 * var(--kole-m-layout-gutter)) / 3); }
.kole-m-layout .kole-m-layout__col--6 { flex-basis: calc((100% - var(--kole-m-layout-gutter)) / 2); }
.kole-m-layout .kole-m-layout__col--4 { flex-basis: calc((100% - 2 * var(--kole-m-layout-gutter)) / 3); }
.kole-m-layout .kole-m-layout__col--3 { flex-basis: calc((100% - 3 * var(--kole-m-layout-gutter)) / 4); }
.kole-m-layout .kole-m-layout__col--2 { flex-basis: calc((100% - 5 * var(--kole-m-layout-gutter)) / 6); }
.kole-m-layout .kole-m-layout__col--auto { flex: 1 1 auto; flex-basis: 0; }

/* 列内容基座:业务把卡片 / 列表放进列里;本类只提供内边距与最小高度示例 */
.kole-m-layout__panel {
  box-sizing: border-box;
  min-height: 48px;
  padding: var(--kole-space-12);
  border-radius: var(--kole-radius-medium);
  background: var(--kole-color-page-bg);
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-label);
  line-height: 1.4;
}

/* 整行占满的横条列(如「更多」入口),保持 44px 触控高度 */
.kole-m-layout__col--bar {
  display: flex;
  align-items: center;
  min-height: var(--kole-m-touch-target);
  padding: 0 var(--kole-space-12);
  border-radius: var(--kole-radius-medium);
  background: var(--kole-color-table-header-bg);
  color: var(--kole-color-text-secondary);
  font-size: var(--kole-m-font-size-label);
}
frameworks-mobile/Layout.html · H5 原生(无框架) · 122 行
frameworks-mobile/Layout.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 · Layout(H5)</title>
<link rel="stylesheet" href="../.design_library/kole-ui-mobile/colors_and_type.css">
<link rel="stylesheet" href="Layout.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-m-gutter); background: var(--kole-color-card-bg); border-block: 1px solid var(--kole-color-border); }
  .demo-block[hidden] { display: none; }
</style>
</head>
<body>
<div class="demo">
  <section class="demo-block" data-demo="cols-2">
    <p class="demo-label">两等分(columns=2:左右两栏的对照型信息)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--cols-2" data-assert="layout-cols-2">
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">待付款 3</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">待发货 1</div></div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="cols-3">
    <p class="demo-label">三等分(columns=3:金额、数量、状态这类短字段)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--cols-3" data-assert="layout-cols-3">
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">合计 ¥128</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">共 2 件</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">已优惠 ¥20</div></div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="cols-4">
    <p class="demo-label">四等分(columns=4:指标卡 / 图标入口;窄屏不折行)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--cols-4 kole-m-layout--gutter-8" data-assert="layout-cols-4">
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">访问</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">下单</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">支付</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">完成</div></div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="gutter">
    <p class="demo-label">列间距(gutter=0 无缝 / 8 / 16 / 24;无缝只用于靠分割线分栏的场景)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--cols-3 kole-m-layout--gutter-0" data-assert="layout-gutter" style="margin-bottom: var(--kole-space-12)">
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">0</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">0</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">0</div></div>
      </div>
      <div class="kole-m-layout kole-m-layout--cols-3 kole-m-layout--gutter-24">
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">24</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">24</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">24</div></div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="span">
    <p class="demo-label">不等宽(列级 col--8 / col--4:主次分栏,主栏 2/3)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--gutter-16" data-assert="layout-span">
        <div class="kole-m-layout__col kole-m-layout__col--8"><div class="kole-m-layout__panel">订单详情(主栏)</div></div>
        <div class="kole-m-layout__col kole-m-layout__col--4"><div class="kole-m-layout__panel">操作</div></div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="align">
    <p class="demo-label">交叉轴对齐(align=center:列高不同时垂直居中)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--cols-2 kole-m-layout--align-center" data-assert="layout-align">
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">一行</div></div>
        <div class="kole-m-layout__col"><div class="kole-m-layout__panel">两行<br>内容</div></div>
      </div>
    </div>
  </section>

  <section class="demo-block" data-demo="nowrap">
    <p class="demo-label">不换行(wrap=false:横向滚动,看板式横向列)</p>
    <div class="demo-box">
      <div class="kole-m-layout kole-m-layout--gutter-16 is-nowrap" data-assert="layout-nowrap">
        <div class="kole-m-layout__col" style="flex-basis: 140px"><div class="kole-m-layout__panel">第 1 列</div></div>
        <div class="kole-m-layout__col" style="flex-basis: 140px"><div class="kole-m-layout__panel">第 2 列</div></div>
        <div class="kole-m-layout__col" style="flex-basis: 140px"><div class="kole-m-layout__panel">第 3 列</div></div>
      </div>
    </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/Layout.jsx · React · 24 行
frameworks-mobile/Layout.jsx
import React from 'react';
import './Layout.css';

/* 布局(移动端)— 规格 §20;行级 columns 负责等分,列级 span 类负责不等宽覆盖。
   纯排布组件:不绑点击、不带交互角色(规格 §20.6)。 */
export default function Layout({
  gutter = 8,
  columns = 0,
  align = 'start',
  wrap = true,
  children = null,
}) {
  const cls =
    'kole-m-layout' +
    ` kole-m-layout--gutter-${gutter}` +
    (columns ? ` kole-m-layout--cols-${columns}` : '') +
    (align === 'center' ? ' kole-m-layout--align-center' : '') +
    (align === 'end' ? ' kole-m-layout--align-end' : '') +
    (align === 'stretch' ? ' kole-m-layout--align-stretch' : '') +
    (wrap ? '' : ' is-nowrap');

  return <div className={cls}>{children}</div>;
}
frameworks-mobile/Layout.vue2.vue · Vue 2 · 36 行
frameworks-mobile/Layout.vue2.vue
<template>
  <div class="kole-m-layout" :class="layoutClass">
    <slot></slot>
  </div>
</template>

<script>
var ALIGN_CLASS = {
  center: 'kole-m-layout--align-center',
  end: 'kole-m-layout--align-end',
  stretch: 'kole-m-layout--align-stretch'
};

export default {
  name: 'KoleMLayout',
  props: {
    gutter: { type: Number, default: 8 },
    columns: { type: Number, default: 0 },
    align: { type: String, default: 'start' },
    wrap: { type: Boolean, default: true }
  },
  computed: {
    layoutClass: function () {
      return [
        'kole-m-layout--gutter-' + this.gutter,
        this.columns ? 'kole-m-layout--cols-' + this.columns : '',
        ALIGN_CLASS[this.align],
        this.wrap ? '' : 'is-nowrap'
      ].filter(Boolean);
    }
  }
};
</script>

<style src="./Layout.css"></style>
frameworks-mobile/Layout.vue3.vue · Vue 3 · 35 行
frameworks-mobile/Layout.vue3.vue
<template>
  <div class="kole-m-layout" :class="layoutClass">
    <slot></slot>
  </div>
</template>

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

const ALIGN_CLASS = {
  center: 'kole-m-layout--align-center',
  end: 'kole-m-layout--align-end',
  stretch: 'kole-m-layout--align-stretch'
};

const props = defineProps({
  gutter: { type: Number, default: 8 },
  columns: { type: Number, default: 0 },
  align: { type: String, default: 'start' },
  wrap: { type: Boolean, default: true }
});

/* 与 React 端同一套类名组合规则 */
const layoutClass = computed(() =>
  [
    `kole-m-layout--gutter-${props.gutter}`,
    props.columns ? `kole-m-layout--cols-${props.columns}` : '',
    ALIGN_CLASS[props.align],
    props.wrap ? '' : 'is-nowrap'
  ].filter(Boolean)
);
</script>

<style src="./Layout.css"></style>
frameworks-mobile/Layout.uniapp.vue · uni-app(跨端:小程序 / App / H5) · 92 行
frameworks-mobile/Layout.uniapp.vue
<template>
  <view class="kole-m-layout" :class="layoutClass">
    <slot></slot>
  </view>
</template>

<script setup>
/* uni-app 端 · 布局(移动端)— 规格 §20
   跨端差异:用 view(无 DOM 布局差异,flex 行为一致);间距用 rpx
   (750rpx = 视口宽度,16rpx = 8px @375pt);纯排布组件,不绑任何手势。 */
import { computed } from 'vue';

const ALIGN_CLASS = {
  center: 'kole-m-layout--align-center',
  end: 'kole-m-layout--align-end',
  stretch: 'kole-m-layout--align-stretch'
};

const props = defineProps({
  gutter: { type: Number, default: 8 },
  columns: { type: Number, default: 0 },
  align: { type: String, default: 'start' },
  wrap: { type: Boolean, default: true }
});

const layoutClass = computed(() =>
  [
    `kole-m-layout--gutter-${props.gutter}`,
    props.columns ? `kole-m-layout--cols-${props.columns}` : '',
    ALIGN_CLASS[props.align],
    props.wrap ? '' : 'is-nowrap'
  ].filter(Boolean)
);
</script>

<style>
.kole-m-layout {
  --kole-m-layout-gutter: 16rpx;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  box-sizing: border-box;
  width: 100%;
}

.kole-m-layout--gutter-0 { --kole-m-layout-gutter: 0rpx; }
.kole-m-layout--gutter-16 { --kole-m-layout-gutter: 32rpx; }
.kole-m-layout--gutter-24 { --kole-m-layout-gutter: 48rpx; }

.kole-m-layout--align-center { align-items: center; }
.kole-m-layout--align-end { align-items: flex-end; }
.kole-m-layout--align-stretch { align-items: stretch; }

.kole-m-layout.is-nowrap { flex-wrap: nowrap; }

.kole-m-layout__col {
  box-sizing: border-box;
  min-width: 0;
  flex: 0 0 auto;
  flex-basis: 100%;
  margin-bottom: var(--kole-m-layout-gutter);
}

.kole-m-layout--cols-2 > .kole-m-layout__col { flex-basis: calc((100% - var(--kole-m-layout-gutter)) / 2); }
.kole-m-layout--cols-3 > .kole-m-layout__col { flex-basis: calc((100% - 2 * var(--kole-m-layout-gutter)) / 3); }
.kole-m-layout--cols-4 > .kole-m-layout__col { flex-basis: calc((100% - 3 * var(--kole-m-layout-gutter)) / 4); }

.kole-m-layout .kole-m-layout__col--6 { flex-basis: calc((100% - var(--kole-m-layout-gutter)) / 2); }
.kole-m-layout .kole-m-layout__col--auto { flex: 1 1 auto; flex-basis: 0; }

.kole-m-layout__panel {
  box-sizing: border-box;
  min-height: 96rpx;
  padding: 24rpx;
  border-radius: var(--kole-radius-medium);
  background-color: var(--kole-color-page-bg);
  color: var(--kole-color-text-body);
  font-size: var(--kole-m-font-size-label);
}

.kole-m-layout__col--bar {
  display: flex;
  align-items: center;
  min-height: 88rpx;
  padding: 0 24rpx;
  border-radius: var(--kole-radius-medium);
  background-color: var(--kole-color-table-header-bg);
  color: var(--kole-color-text-secondary);
  font-size: var(--kole-m-font-size-label);
}
</style>

测试与回归

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

断言 17 条 · 全部通过 报告 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-layout.json(点击展开原始 JSON)
{
  "schemaVersion": 1,
  "sourceKind": "authored-spec",
  "provenance": "authored-in-repo",
  "specFile": "spec/移动端规格.md",
  "specSection": "20 · 布局 Layout",
  "confidence": "high",
  "slug": "mobile-layout",
  "name": "布局 Layout",
  "semanticTypeCandidates": [
    "layout",
    "grid"
  ],
  "variantDimensions": [
    {
      "name": "gutter",
      "values": [
        "0",
        "8",
        "16",
        "24"
      ]
    },
    {
      "name": "columns",
      "values": [
        "0",
        "2",
        "3",
        "4"
      ]
    },
    {
      "name": "align",
      "values": [
        "start",
        "center",
        "end",
        "stretch"
      ]
    },
    {
      "name": "wrap",
      "values": [
        "true",
        "false"
      ]
    }
  ],
  "representativeVariants": [
    {
      "columns": "2",
      "gutter": "8",
      "label": "两等分(左右对照)"
    },
    {
      "columns": "3",
      "gutter": "16",
      "label": "三等分(短字段)"
    },
    {
      "columns": "4",
      "gutter": "8",
      "label": "四等分(指标卡 / 入口)"
    },
    {
      "columns": "0",
      "gutter": "16",
      "label": "不等宽(列级 span 主次分栏)"
    }
  ],
  "anatomy": {
    "layout": "行容器,display: flex + 换行,承载列间距变量",
    "col": "列,默认占满一行(flex-basis: 100%),由行级等分或列级 span 类决定实际宽度",
    "panel": "列内容的可选包壳,提供内边距与最小高度,业务也可直接放自己的卡片",
    "gutter": "列间距,四档(0 / 8 / 16 / 24),由行级类写到 CSS 变量上",
    "span": "列级权重类(12 栅格取值),可覆盖行级等分"
  },
  "structurePatterns": {
    "gutter": "0 无缝 / 8 紧凑 / 16 常规 / 24 宽松,由 --kole-m-layout-gutter 承载",
    "columns": "0 不启用等分 / 2 / 3 / 4 等分(行级)",
    "align": "start / center / end / stretch(交叉轴)",
    "wrap": "true 换行(默认)/ false 横向滚动"
  },
  "usageHints": [
    "把一行内容按比例切成若干列(等分或按 12 栅格取值)",
    "布局容器不绑点击,不设 cursor: pointer,热区始终由列内的业务元素提供",
    "列内若是可点区域,其热区不小于 44×44(见 §19.5 与 §6.5)",
    "gutter 由 CSS 变量承载,同一行内所有列的宽度计算共用该变量,避免手工算宽",
    "列宽使用 flex-basis: calc(...),窄屏下不产生横向溢出(列内文本默认 min-width: 0)"
  ],
  "doNotInvent": [
    "响应式断点(移动端只有一档列数,不为大屏定义断点)",
    "列的排序 / 拖拽",
    "栅格嵌套的层级规则"
  ],
  "unknowns": [
    "列内推荐的最大列数(本实现提供 2 / 3 / 4 三档)",
    "列高等分(stretch 之外的等高策略)"
  ],
  "interaction": [
    "布局容器不绑点击,不设 cursor: pointer,热区始终由列内的业务元素提供",
    "列内若是可点区域,其热区不小于 44×44(见 §19.5 与 §6.5)",
    "gutter 由 CSS 变量承载,同一行内所有列的宽度计算共用该变量,避免手工算宽",
    "wrap=false 时容器横向滚动,纵向页面滚动不受影响"
  ],
  "accessibility": [
    "布局是纯视觉分组:不添加 role、不添加 aria-*",
    "不要为了布局把语义节点(列表 / 按钮)拆到不相邻的列里,否则读屏顺序会错乱",
    "视觉顺序必须与 DOM 顺序一致(不用 order 重排)"
  ],
  "api": {
    "source": "implementation",
    "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs",
    "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。",
    "props": [
      {
        "name": "gutter",
        "type": "0 | 8 | 16 | 24",
        "default": "8",
        "desc": "变体 gutter:列间距四档,由 CSS 变量承载(规格 §20.3)",
        "required": false
      },
      {
        "name": "columns",
        "type": "0 | 2 | 3 | 4",
        "default": "0",
        "desc": "变体 columns:行级等分列数,0 表示不启用等分(规格 §20.3)",
        "required": false
      },
      {
        "name": "align",
        "type": "'start' | 'center' | 'end' | 'stretch'",
        "default": "'start'",
        "desc": "变体 align:交叉轴对齐(规格 §20.3)",
        "required": false
      },
      {
        "name": "wrap",
        "type": "boolean",
        "default": "true",
        "desc": "变体 wrap:false 时不换行、横向滚动(规格 §20.3)",
        "required": false
      }
    ],
    "events": [],
    "slots": [
      {
        "name": "default",
        "desc": "列(.kole-m-layout__col)或任意业务内容(规格 §20.2)"
      }
    ]
  },
  "variantClasses": {
    "gutter": {
      "0": [
        ".kole-m-layout--gutter-0"
      ],
      "8": [
        ".kole-m-layout--gutter-8",
        "--kole-m-layout-gutter"
      ],
      "16": [
        ".kole-m-layout--gutter-16"
      ],
      "24": [
        ".kole-m-layout--gutter-24"
      ]
    },
    "columns": {
      "0": [],
      "2": [
        ".kole-m-layout--cols-2"
      ],
      "3": [
        ".kole-m-layout--cols-3"
      ],
      "4": [
        ".kole-m-layout--cols-4"
      ]
    },
    "align": {
      "start": [],
      "center": [
        ".kole-m-layout--align-center"
      ],
      "end": [
        ".kole-m-layout--align-end"
      ],
      "stretch": [
        ".kole-m-layout--align-stretch"
      ]
    },
    "wrap": {
      "true": [],
      "false": [
        ".is-nowrap"
      ]
    }
  },
  "demos": [
    {
      "id": "cols-2",
      "group": "01 组件类型",
      "title": "两等分",
      "desc": "左右对照型信息(待付款 / 待发货)用两等分,列宽自动均分不手算。",
      "variant": "columns=2 / gutter=8"
    },
    {
      "id": "cols-3",
      "group": "01 组件类型",
      "title": "三等分",
      "desc": "金额、数量、优惠这类短字段用三等分;列内文字过长会换行而不是撑破列宽。",
      "variant": "columns=3 / gutter=16"
    },
    {
      "id": "cols-4",
      "group": "01 组件类型",
      "title": "四等分",
      "desc": "指标卡与图标入口最常用的档位;窄屏下保持四列,不折行。",
      "variant": "columns=4 / gutter=8"
    },
    {
      "id": "gutter",
      "group": "02 组件状态",
      "title": "列间距",
      "desc": "0 无缝只用于靠分割线或底色分栏的场景;常规内容用 8 或 16。",
      "variant": "gutter=0 | 24"
    },
    {
      "id": "span",
      "group": "02 组件状态",
      "title": "不等宽",
      "desc": "主次分栏用列级 span 覆盖行级等分:主栏 8/12,次栏 4/12。",
      "variant": "col--8 + col--4"
    },
    {
      "id": "align",
      "group": "02 组件状态",
      "title": "交叉轴对齐",
      "desc": "列高不同时用 align=center 让内容垂直居中,默认 start 顶部对齐。",
      "variant": "align=center"
    },
    {
      "id": "nowrap",
      "group": "02 组件状态",
      "title": "不换行",
      "desc": "wrap=false 时列横向滚动,纵向页面滚动不受影响(看板式横向列)。",
      "variant": "wrap=false"
    }
  ],
  "related": [
    {
      "slug": "mobile-grid",
      "why": "等分的图标 / 文字入口用宫格(自带图标与文字样式);只是切分版面才用布局"
    },
    {
      "slug": "mobile-divider",
      "why": "列之间要靠线分栏时用分割线;布局只管间距与宽度"
    },
    {
      "slug": "cell",
      "why": "一行一个语义项(可点整行)用单元格;一行要放多个并列块才用布局"
    }
  ]
}