7.3 布局系统
> **本节目标**:掌握 RML 的布局系统——Flexbox 布局、定位、对齐、间距。
7.3.1 布局的核心:Flexbox
RML 主推 Flexbox 布局,简化了布局的复杂性:
┌─────────────────────────────────────────┐
│ Flex 容器 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 子元素 │ │ 子元素 │ │ 子元素 │ │ 子元素 │ │ ← 主轴(main axis)
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ ← 交叉轴(cross axis)→ │
└─────────────────────────────────────────┘
7.3.2 Flex 容器
创建 Flex 容器
.container {
display: flex; /* 水平排列 */
}
<div class="container">
<div>子元素 1</div>
<div>子元素 2</div>
<div>子元素 3</div>
</div>
flex-direction:主轴方向
/* 水平排列(默认) */
.container { flex-direction: row; }
/* 水平反向排列 */
.container { flex-direction: row-reverse; }
/* 垂直排列 */
.container { flex-direction: column; }
/* 垂直反向排列 */
.container { flex-direction: column-reverse; }
row: column:
┌──┬──┬──┐ ┌──┐
│1 │2 │3 │ │1 │
└──┴──┴──┘ ├──┤
│2 │
├──┤
│3 │
└──┘
justify-content:主轴对齐
/* 起点对齐 */
.container { justify-content: flex-start; }
/* 终点对齐 */
.container { justify-content: flex-end; }
/* 居中对齐 */
.container { justify-content: center; }
/* 两端对齐,子元素间距相等 */
.container { justify-content: space-between; }
/* 均匀分布,子元素周围间距相等 */
.container { justify-content: space-around; }
/* 均匀分布,子元素之间和两端间距相等 */
.container { justify-content: space-evenly; }
flex-start: |123 |
flex-end: | 123|
center: | 123 |
space-between: |1 2 3 |
space-around: | 1 2 3 |
space-evenly: | 1 2 3 |
align-items:交叉轴对齐
/* 拉伸填满(默认) */
.container { align-items: stretch; }
/* 起点对齐 */
.container { align-items: flex-start; }
/* 终点对齐 */
.container { align-items: flex-end; }
/* 居中对齐 */
.container { align-items: center; }
/* 基线对齐 */
.container { align-items: baseline; }
flex-wrap:换行
/* 不换行(默认) */
.container { flex-wrap: nowrap; }
/* 换行 */
.container { flex-wrap: wrap; }
/* 反向换行 */
.container { flex-wrap: wrap-reverse; }
align-content:多行对齐
.container {
flex-wrap: wrap;
align-content: flex-start; /* 多行起点对齐 */
align-content: center; /* 多行居中 */
align-content: space-between; /* 多行两端对齐 */
}
gap:间距
.container {
display: flex;
gap: 10px; /* 行和列间距都是 10px */
gap: 10px 20px; /* 行间距 10px,列间距 20px */
row-gap: 10px; /* 行间距 */
column-gap: 20px; /* 列间距 */
}
7.3.3 Flex 子元素
flex-grow:放大比例
.item {
flex-grow: 1; /* 占据剩余空间 */
}
.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 2; } /* 占据 item-1 的两倍空间 */
flex-shrink:缩小比例
.item {
flex-shrink: 0; /* 不缩小 */
}
flex-basis:初始大小
.item {
flex-basis: 200px; /* 初始大小 200px */
flex-basis: auto; /* 根据内容计算 */
}
flex 简写
.item {
flex: 1; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0% */
flex: 2; /* flex-grow: 2 */
flex: 1 0 200px; /* flex-grow: 1, flex-shrink: 0, flex-basis: 200px */
flex: auto; /* flex: 1 1 auto */
flex: none; /* flex: 0 0 auto */
}
align-self:单独对齐
.item {
align-self: center; /* 单独居中对齐 */
align-self: flex-start;
align-self: flex-end;
}
order:排序
.item-1 { order: 2; }
.item-2 { order: 1; } /* item-2 显示在 item-1 前面 */
7.3.4 常见布局模式
模式一:水平居中
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
<div class="container" style="height: 200px;">
<div>居中内容</div>
</div>
模式二:两栏布局
.layout {
display: flex;
height: 100vh;
}
.sidebar {
flex: 0 0 250px; /* 固定宽度 250px */
background: #f5f5f5;
}
.main {
flex: 1; /* 占据剩余空间 */
padding: 20px;
}
<div class="layout">
<div class="sidebar">侧边栏</div>
<div class="main">主内容</div>
</div>
模式三:三栏布局
.layout {
display: flex;
height: 100vh;
}
.left {
flex: 0 0 200px;
}
.center {
flex: 1;
}
.right {
flex: 0 0 250px;
}
模式四:垂直布局
.vertical-layout {
display: flex;
flex-direction: column;
height: 100vh;
}
.header {
flex: 0 0 60px; /* 固定高度 */
}
.content {
flex: 1; /* 占据剩余空间 */
overflow: auto;
}
.footer {
flex: 0 0 40px;
}
<div class="vertical-layout">
<div class="header">头部</div>
<div class="content">内容</div>
<div class="footer">页脚</div>
</div>
模式五:网格布局(用 Flexbox 模拟)
.grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.grid-item {
flex: 0 0 calc(33.333% - 16px); /* 3 列 */
/* flex: 0 0 calc(25% - 16px); */ /* 4 列 */
/* flex: 0 0 calc(50% - 16px); */ /* 2 列 */
}
模式六:圣杯布局
.holy-grail {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.holy-grail-header,
.holy-grail-footer {
flex: 0 0 auto;
}
.holy-grail-body {
display: flex;
flex: 1;
}
.holy-grail-content {
flex: 1;
}
.holy-grail-nav,
.holy-grail-aside {
flex: 0 0 200px;
}
.holy-grail-nav {
order: -1; /* 导航在左侧 */
}
<div class="holy-grail">
<div class="holy-grail-header">头部</div>
<div class="holy-grail-body">
<div class="holy-grail-content">主内容</div>
<div class="holy-grail-nav">导航</div>
<div class="holy-grail-aside">侧边栏</div>
</div>
<div class="holy-grail-footer">页脚</div>
</div>
7.3.5 定位
position 属性
/* 静态定位(默认) */
.element { position: static; }
/* 相对定位 */
.element { position: relative; }
/* 绝对定位 */
.element { position: absolute; }
/* 固定定位 */
.element { position: fixed; }
/* 粘性定位 */
.element { position: sticky; }
相对定位
.element {
position: relative;
top: 10px; /* 相对原位置向下 10px */
left: 20px; /* 相对原位置向右 20px */
}
绝对定位
.parent {
position: relative; /* 父元素相对定位 */
height: 200px;
}
.child {
position: absolute;
top: 50%; /* 相对父元素顶部 50% */
left: 50%; /* 相对父元素左侧 50% */
transform: translate(-50%, -50%); /* 居中 */
}
固定定位
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100; /* 确保在最上层 */
}
z-index:层级
.modal-overlay {
position: fixed;
z-index: 1000;
}
.modal {
position: fixed;
z-index: 1001; /* 比遮罩层高 */
}
7.3.6 溢出处理
overflow 属性
/* 可见(默认) */
.element { overflow: visible; }
/* 隐藏 */
.element { overflow: hidden; }
/* 滚动 */
.element { overflow: scroll; }
/* 自动滚动 */
.element { overflow: auto; }
/* 分别设置 x 和 y */
.element {
overflow-x: hidden;
overflow-y: auto;
}
RML 布尔属性(推荐)
在 `.rml` 元素上可直接写 Tailwind 风格布尔标志(无需 `style`):
<div overflow-y-auto="" style="height: 200px;">
<p each={item in items}>{item}</p>
</div>
| 属性 | 效果 | |------|------| | `overflow-y-auto` / `overflow-y-scroll` | 垂直滚动 | | `overflow-x-auto` / `overflow-x-scroll` | 水平滚动 | | `overflow-y-hidden` / `overflow-x-hidden` | 单轴隐藏 | | `overflow-hidden` | 双轴隐藏 |
等价于 `overflow-y="auto"` 等归一化样式属性。案例见 `overflow_test_case`。
文本溢出
.text-ellipsis {
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 隐藏溢出 */
text-overflow: ellipsis; /* 显示省略号 */
}
7.3.7 响应式布局
RML 支持简单的响应式布局,通过窗口尺寸调整:
媒体查询(部分支持)
/* 默认样式(小窗口) */
.grid-item {
flex: 0 0 100%;
}
/* 中等窗口 */
@media (min-width: 768px) {
.grid-item {
flex: 0 0 50%;
}
}
/* 大窗口 */
@media (min-width: 1200px) {
.grid-item {
flex: 0 0 33.333%;
}
}
通过数据绑定响应
<div class="grid">
<div
each={item in items}
class={is_compact ? "grid-item-compact" : "grid-item"}
>
{item.name}
</div>
</div>
#[computed]
pub fn is_compact(&self) -> bool {
self.window_width < 768.0
}
7.3.8 完整示例:仪表盘布局
<!-- views/dashboard.rml -->
<div class="dashboard">
<!-- 顶部导航 -->
<header class="dashboard-header">
<div class="header-left">
<h1 class="header-title">仪表盘</h1>
</div>
<div class="header-right">
<button class="btn-icon" on-click={toggle_notifications}>
🔔
</button>
<Avatar src={user.avatar} />
</div>
</header>
<!-- 主体内容 -->
<div class="dashboard-body">
<!-- 侧边栏 -->
<aside class="dashboard-sidebar">
<nav class="sidebar-nav">
<ul class="nav-list">
<li class="nav-item active">
<a on-click={show_overview}>概览</a>
</li>
<li class="nav-item">
<a on-click={show_analytics}>分析</a>
</li>
<li class="nav-item">
<a on-click={show_settings}>设置</a>
</li>
</ul>
</nav>
</aside>
<!-- 主内容区 -->
<main class="dashboard-main">
<!-- 统计卡片 -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-info">
<div class="stat-label">总用户</div>
<div class="stat-value">{total_users}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">💰</div>
<div class="stat-info">
<div class="stat-label">总收入</div>
<div class="stat-value">{total_revenue}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">📦</div>
<div class="stat-info">
<div class="stat-label">订单数</div>
<div class="stat-value">{total_orders}</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">📈</div>
<div class="stat-info">
<div class="stat-label">增长率</div>
<div class="stat-value">{growth_rate}%</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="chart-area">
<div class="chart-card">
<h2 class="chart-title">销售趋势</h2>
<div class="chart-container">
<!-- 图表内容 -->
</div>
</div>
</div>
<!-- 列表区域 -->
<div class="list-area">
<div class="list-card">
<h2 class="list-title">最近订单</h2>
<ul class="order-list">
<li each={order in recent_orders} key={order.id} class="order-item">
<span class="order-id">#{order.id}</span>
<span class="order-customer">{order.customer_name}</span>
<span class="order-amount">¥{order.amount}</span>
<span class="order-status {order.status_class}">{order.status}</span>
</li>
</ul>
</div>
</div>
</main>
</div>
</div>
/* src/styles/dashboard.css */
.dashboard {
display: flex;
flex-direction: column;
height: 100vh;
background: #f5f5f5;
}
/* 顶部导航 */
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
height: 60px;
background: white;
border-bottom: 1px solid #e0e0e0;
flex-shrink: 0;
}
.header-title {
font-size: 20px;
font-weight: bold;
color: #333;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
/* 主体内容 */
.dashboard-body {
display: flex;
flex: 1;
overflow: hidden;
}
/* 侧边栏 */
.dashboard-sidebar {
flex: 0 0 240px;
background: #2c3e50;
color: white;
overflow-y: auto;
}
.sidebar-nav {
padding: 16px 0;
}
.nav-list {
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
padding: 0;
}
.nav-item a {
display: block;
padding: 12px 24px;
color: #ecf0f1;
text-decoration: none;
cursor: pointer;
}
.nav-item.active a {
background: #34495e;
border-left: 3px solid #3498db;
}
.nav-item a:hover {
background: #34495e;
}
/* 主内容区 */
.dashboard-main {
flex: 1;
padding: 24px;
overflow-y: auto;
}
/* 统计卡片网格 */
.stats-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
flex: 1 1 calc(25% - 16px);
min-width: 200px;
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.stat-icon {
font-size: 32px;
}
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
/* 图表区域 */
.chart-area {
margin-bottom: 24px;
}
.chart-card {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.chart-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
}
.chart-container {
height: 300px;
background: #f9f9f9;
border-radius: 4px;
}
/* 列表区域 */
.list-card {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.list-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 16px;
color: #333;
}
.order-list {
list-style: none;
padding: 0;
margin: 0;
}
.order-item {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.order-item:last-child {
border-bottom: none;
}
.order-id {
flex: 0 0 80px;
font-weight: bold;
color: #333;
}
.order-customer {
flex: 1;
color: #666;
}
.order-amount {
flex: 0 0 100px;
text-align: right;
font-weight: bold;
color: #333;
}
.order-status {
flex: 0 0 80px;
text-align: center;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.order-status.pending {
background: #fff3cd;
color: #856404;
}
.order-status.completed {
background: #d4edda;
color: #155724;
}
.order-status.cancelled {
background: #f8d7da;
color: #721c24;
}
7.3.9 小结
RML 的布局系统:
- **Flexbox**:主推布局方式,简化布局 - **容器属性**:`flex-direction`、`justify-content`、`align-items`、`flex-wrap`、`gap` - **子元素属性**:`flex-grow`、`flex-shrink`、`flex-basis`、`order`、`align-self` - **定位**:`position`、`top`、`right`、`bottom`、`left`、`z-index` - **溢出**:`overflow`、`text-overflow` - **响应式**:媒体查询(部分)、数据绑定
下一节 → [7.4 主题与皮肤](./theming.md)