简体中文
页面布局
搭界面要从外往里搭。先把页面的骨架和每一块占多大地方定下来,再往里填内容。反过来做,先写内容再补容器,最后每一段都会自己发明一个盒子,页面就变成一列层层包着的卡片。
下面四步,一步一步往里走。
- 选外壳:整页用
AppShell,还是页面里的一块用Layout。 - 分区域:页眉、正文、页脚、侧边各放什么。
- 对齐:所有区域共用一条内容线。
- 调间距:先定间距,再决定要不要密度更紧的组件。
第一步:选外壳
整页的外框交给 AppShell。它管顶栏、侧导航、横幅、正文,还会在窄屏把侧导航收进抽屉。
vue
<script setup lang="ts">
import { AppShell } from '@astryx-vue/core/AppShell'
import Nav from './Nav.vue'
import PageBody from './PageBody.vue'
// TopBar、Nav、PageBody 换成你自己的组件
import TopBar from './TopBar.vue'
</script>
<template>
<AppShell :content-padding="0" :mobile-nav="{ breakpoint: 'md' }">
<template #topNav>
<TopBar />
</template>
<template #sideNav>
<Nav />
</template>
<PageBody />
</AppShell>
</template>AppShell 的插槽有 topNav、sideNav、banner、默认的正文,以及窄屏抽屉用的 mobileNav。
contentPadding 是正文区的内边距,用间距刻度写。不传就贴边,由里面的 Layout 自己管边距——所以外壳和内层不要同时加内边距,不然会叠成两倍。
页面里的一块——一张卡片、一个对话框、Tab 里的一栏——用 Layout。Layout 不负责整页,它只在自己的范围里分区域。
第二步:分区域
Layout 最多分五块,默认插槽是正文,其余是具名插槽:
| 插槽 | 放什么 |
|---|---|
header | 这一块的标题和主操作 |
start | 起始边的侧栏或面板 |
| 默认插槽 | 主要内容,通常包一层 LayoutContent |
end | 结束边的侧栏或面板 |
footer | 提交、取消这类必须一直够得着的按钮 |
vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Layout, LayoutContent, LayoutFooter, LayoutHeader } from '@astryx-vue/core/Layout'
</script>
<template>
<Layout :padding="4" default-has-dividers>
<template #header>
<LayoutHeader>订单详情</LayoutHeader>
</template>
<LayoutContent>
正文内容,超出时只在这一块里滚动。
</LayoutContent>
<template #footer>
<LayoutFooter>
<Button label="保存" />
</LayoutFooter>
</template>
</Layout>
</template>height 取 fill(默认)时,Layout 撑满容器,只有正文在中间滚动,页眉和页脚留在原地。取 auto 时随内容长高,整页一起滚。
页眉、页脚、正文都齐了以后,把它们之间的线交给 defaultHasDividers 一次开,不用每个区域各写一个 hasDivider。
第三步:对齐同一条内容线
Layout 的 padding 只写一次,它落在所有区域上:页眉、正文、页脚、两侧面板的左右内边距用的是同一个值。所以它们的文字自然对齐在同一条竖线上。 这条线会被两种写法打断,都很好躲:
- 在
LayoutContent里再包一层带padding的div,正文就比页眉往里缩了一截。 - 在页眉里自己写
padding,页眉就比正文往外多了一截。
要缩进某一块,用那个区域组件自己的 padding,或者干脆用 HStack 加 gap,别在区域外面再套一层盒子。
正文太长不好读时,用 contentWidth 限制宽度:数字按像素,也可以写 '100%'、'40rem'。宽度收紧以后,正文在中间居中,上下的分隔线还是拉满整行。
第四步:排局部,调间距
区域里再往下分,就该 HStack、VStack、Grid 出场了。它们只负责摆放,不画边框也不给底色。
vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Grid } from '@astryx-vue/core/Grid'
import { HStack, VStack } from '@astryx-vue/core/Stack'
// OrderCard 换成你自己的组件
import OrderCard from './OrderCard.vue'
</script>
<template>
<!-- 一行:标题在左,按钮在右 -->
<HStack justify="between" align="center" :gap="3">
<span>最近的订单</span>
<Button label="刷新" variant="secondary" />
</HStack>
<!-- 一列:每两块之间 16px -->
<VStack :gap="4">
<OrderCard />
<OrderCard />
</VStack>
<!-- 自动增减列:每列至少 240px,最多 4 列 -->
<Grid :columns="{ minWidth: 240, max: 4 }" :gap="3">
<Card v-for="item in items" :key="item.id" />
</Grid>
</template>gap、padding 收的是间距刻度:2 就是 --astryx-spacing-2,也就是 8px。写刻度而不是写 16px,主题换了、根字号变了,间距会跟着一起动。
Grid 的 columns 写数字就是固定那么多列(日历那种一周七格适合这么写);写 { minWidth, max } 就是按容器宽度自动增减列数。
间距定完,再看密度。同样一块内容,可以调的不止间距:
- 表格、列表这类一行一条的密集数据,用行和分隔线,不要每条包一张卡片。
Card留给独立的小部件、图集和设置分组。- 侧栏、抽屉、卡片里放不下完整版式时,换成紧凑形态的组件(例如
EmptyState的isCompact)。
该做与不该做
| 指引 | 实践 |
|---|---|
| 推荐 | 先定外壳和区域,再写内容。 |
| 推荐 | 内边距只写一次,写在 Layout 或 AppShell 上。 |
| 推荐 | 区域之间的边界用分隔线,不用留白硬撑。 |
| 避免 | 每个区域各写一套 padding,文字线对不齐。 |
| 避免 | 每一块内容都包一张 Card,页面变成一列盒子。 |
| 避免 | 用 div 加内联 margin 代替 HStack、VStack、Grid。 |