HarmonyOS NEXT ArkTS 仪表盘布局实战——Grid + 卡片组合构建管理后台


一、引言:管理后台的布局挑战与鸿蒙方案
1.1 从移动端到管理后台
在移动应用开发领域,“仪表盘”(Dashboard)是一个经久不衰的界面模式。无论是电商平台的数据监控、企业应用的运营看板,还是 IoT 设备的管理后台,仪表盘始终是信息密度最高、布局要求最严格的界面类型之一。
一个典型的仪表盘界面通常包含以下要素:
- KPI 指标卡片:展示核心业务数据(用户数、收入、转化率等)
- 数据可视化区域:折线图、柱状图、饼图等图表占位
- 最近活动/订单列表:表格形式展示最新动态
- 顶部导航/标题栏:应用标识和用户操作入口
在传统的移动端开发中,实现这样的布局通常需要多层嵌套的 LinearLayout(Android)、UIStackView(iOS)或 Flexbox(React Native),开发者需要手动计算每个卡片的宽度、间距和换行逻辑。而在鸿蒙 ArkUI 中,Grid 组件 以声明式的方式彻底解决了这个问题。
1.2 HarmonyOS NEXT 6.1.1 与 ArkUI 布局能力
HarmonyOS NEXT 6.1.1(API 24)是鸿蒙生态的重要里程碑。这一版本完全移除了 Android AOSP 代码,基于纯鸿蒙内核运行。对于开发者而言,这意味着应用必须以 ArkTS 语言和 ArkUI 框架进行原生开发。
ArkUI 提供了完整的声明式 UI 能力,其核心布局组件包括:
| 组件 | 用途 | 适用场景 |
|---|---|---|
| Column | 纵向弹性布局 | 页面整体架构、纵向列表 |
| Row | 横向弹性布局 | 标题栏、输入栏、行内对齐 |
| Grid | 二维网格布局 | 仪表盘卡片网格、图片画廊、日历 |
| Flex | 弹性盒布局 | 自适应换行、等比分布 |
| Stack | 层叠布局 | 浮动按钮、遮罩层、背景叠加 |
在这些组件中,Grid 是最适合仪表盘场景的布局方案——它天然支持行列定义,配合 columnsTemplate 和 rowsTemplate 属性,开发者可以用寥寥几行代码创建出规整的卡片网格。
1.3 本文的实践内容
本文将以一个完整的 Dashboard 仪表盘页面 为例,深度剖析以下技术:
- Grid 组件:columnsTemplate、rowsTemplate、GridItem 完整用法
- 卡片设计:渐变背景(linearGradient)、阴影(shadow)、圆角
- 状态管理:@State 在静态展示页中的最佳实践
- 组件化:@Builder 拆分模块化 UI
- 暗色主题:深色仪表盘的配色方案与视觉设计
- Scroll + layoutWeight:可滚动内容区域实现
项目基于 HarmonyOS NEXT 6.1.1(API 24),编译链为 Hvigor 6.1.1,所有代码均已在 DevEco Studio 中构建通过。
二、仪表盘的整体布局架构
2.1 七层布局结构
Dashboard 页面的布局采用经典的"固定头部 + 可滚动内容"模式,从上到下分为七个层次:
Column (全屏, 深空蓝黑 #0F1923 底色)
├── Row ① 顶部导航栏 (固定高度 56vp)
│ ├── Row Logo + "Dashboard" 文字
│ └── Row 通知图标 + 用户头像
│
├── Scroll (layoutWeight:1) ② 可滚动主体 (弹性占满)
│ └── Column (padding:16)
│ │
│ ├── Column ③ 欢迎横幅 (阴影卡片)
│ │ ├── Row 标题 + 日期 + "新建"按钮
│ │ └── Row 三个统计标签 (本月订单/新增用户/好评率)
│ │
│ ├── Text ④ 区域标题 "📈 核心指标"
│ ├── Grid (2列 × 2行) ⑤ ★★★ KPI 卡片网格 ★★★
│ │ ├── GridItem [蓝] 总用户数 12,846 ↑+12.5%
│ │ ├── GridItem [绿] 总收入 ¥86,942 ↑+8.3%
│ │ ├── GridItem [橙] 活跃用户 6,231 ↓-2.1%
│ │ └── GridItem [紫] 转化率 24.8% ↑+3.7%
│ │
│ ├── Text ⑥ 区域标题 "📊 数据分析"
│ ├── Grid (2列 × 1行) ⑦ ★★★ 图表卡片网格 ★★★
│ │ ├── GridItem 销售趋势 → 柱状模拟图
│ │ └── GridItem 渠道分布 → 柱状模拟图
│ │
│ ├── Text ⑧ 区域标题 "📋 最近订单"
│ └── Column ⑨ 订单列表卡片
│ ├── Row 表头行
│ ├── Row × 4 订单数据行 (带彩色状态标签)
│ └── Divider × 3 行间分隔线
│
└── Blank().height(24) ⑩ 底部留白
这个架构的核心设计理念是 “固定 + 弹性 + 固定”:
- 顶部标题栏固定 56vp
- 中间内容区域通过 Scroll +
layoutWeight(1)弹性占满 - 底部通过空白留白而非固定栏,保持内容完整性
2.2 配色方案设计
Dashboard 采用了深色主题配色方案,这是目前管理后台的主流趋势:
背景底色: #0F1923 (深空蓝黑)
卡片底色: #1E2D3D (深蓝灰)
导航栏底色: #1A2A3A (略浅于卡片)
文字主色: #FFFFFF (白色)
文字次要色: #B0C4DE / #8899AA (浅灰蓝)
强调色蓝: #4A90D9
强调色绿: #27AE60
强调色橙: #E67E22
强调色紫: #8E44AD
上涨色: #2ECC71 (绿色)
下跌色: #E74C3C (红色)
四张 KPI 卡片采用不同的渐变色,既区分类别又增加视觉层次感。这种配色方案与 AirBnb、Stripe 等现代 SaaS 产品的设计语言一致。
三、核心布局技术一:Grid 组件深度解析
3.1 Grid 的基本概念
Grid 是 ArkUI 中用于创建二维网格布局的核心组件。与 Row/Column 的线性排列不同,Grid 允许组件在水平和垂直两个方向上同时排列。
Grid 的核心概念包括:
- 列模板(columnsTemplate):定义网格的列数和每列宽度
- 行模板(rowsTemplate):定义网格的行数和每行高度
- 网格项(GridItem):网格中的每个单元格,作为 Grid 的直接子组件
- 间距(columnsGap / rowsGap):列与列、行与行之间的间隔
3.2 fr 单位详解
fr(fraction unit,分数单位)是 Grid 和 Flex 布局中分配剩余空间的专用单位。理解 fr 是掌握 Grid 布局的关键。
fr 的计算规则:
当 columnsTemplate 设置为 '1fr 1fr' 时:
- 先计算所有非 fr 单位的列的总宽度(本例中没有非 fr 列)
- 将 Grid 总宽度减去非 fr 列宽度,得到剩余空间
- 将剩余空间除以 fr 总数(1+1=2),每份 fr 获得剩余空间的 1/2
因此 '1fr 1fr' 意味着两列各占 50% 宽度。
如果设置为 '1fr 2fr 1fr',则三列的比例为 1:2:1,即 25%、50%、25%。
fr 与百分比的区别:
| 特性 | fr | 百分比 |
|---|---|---|
| 计算基准 | 剩余空间 | 父容器总宽度 |
| 受其他列影响 | 是(fr 总量变化影响分配) | 否(各自独立) |
| 与非 fr 列兼容 | 是(先分配非 fr 列) | 需手动计算剩余 |
| 嵌套场景 | 自动适应 | 需逐层计算 |
在实际开发中,fr 比百分比更灵活,特别是在与固定宽度的列配合使用时。
3.3 KPI 网格的 Grid 配置
KPI 指标卡片区域的 Grid 配置如下:
Grid() {
ForEach(this.kpiCards, (card: KpiCardData) => {
GridItem() {
// 卡片内容
}
.width('100%')
.height(140)
.borderRadius(16)
.linearGradient({ /* ... */ })
.shadow({ /* ... */ })
})
}
.columnsTemplate('1fr 1fr') // ★★★ 2列等宽 ★★★
.rowsTemplate('1fr 1fr') // ★★★ 2行等高 ★★★
.columnsGap(12) // 列间距 12vp
.rowsGap(12) // 行间距 12vp
.width('100%')
.height(140 * 2 + 12) // 总高度 = 行高×2 + 行间距
这里有几个关键点需要特别注意:
rowsTemplate 的使用:KPI 网格固定为 2 行,因此设置 rowsTemplate('1fr 1fr') 明确指定行高比例。两个 1fr 意味着两行高度相等,都是父容器分配高度的一半。Grid 的父容器高度通过 .height(140 * 2 + 12) 手动设定,其中 140 是每行高度,12 是行间距。
GridItem 的 height 与 Grid 的 rowsTemplate 的关系:GridItem 上设置的 height(140) 实际上是 GridItem 自身的最小高度请求,而 Grid 的 rowsTemplate('1fr 1fr') 会等分 Grid 的总高度给两行。当 Grid 高度被设置为 140*2+12=292vp 时,两行各获得 140vp,与 GridItem 请求的高度一致。
3.4 图表网格的 Grid 配置
图表区域只有一行两列:
Grid() {
ForEach(this.chartCards, (chart: ChartCardData) => {
GridItem() {
// 图表卡片内容
}
.width('100%')
.height(180)
.backgroundColor('#1E2D3D')
.borderRadius(16)
.shadow({ /* ... */ })
})
}
.columnsTemplate('1fr 1fr') // ★★★ 2列等宽 ★★★
.rowsTemplate('1fr') // ★★★ 1行 ★★★
.columnsGap(12)
.width('100%')
.height(180) // 单行高度 180vp
这里的 rowsTemplate('1fr') 表示只有一行,占据 Grid 的全部高度。Grid 的总高度通过 .height(180) 设置。
3.5 Grid 与 Scroll 的配合
仪表盘整体包裹在 Scroll 中实现垂直滚动。Grid 本身不滚动,而是作为 Scroll 的子内容。
Scroll (layoutWeight:1)
└── Column
├── 欢迎横幅 ← 静态卡片
├── Grid ← 静态2×2网格
├── Grid ← 静态2×1网格
└── Column ← 订单列表
Grid 的 scrollBarState(BarState.Off) 确保 Grid 内部不产生滚动条。所有滚动行为由外层的 Scroll 统一管理。
为什么 Grid 内部没有滚动? 因为 Grid 的 rowsTemplate 指定了固定的行数(KPI 网格 2 行,图表网格 1 行),且 Grid 的高度被固定为恰好容纳这些行。Grid 的内容不会超过其容器高度,因此不需要内部滚动。
四、核心布局技术二:卡片视觉设计
4.1 线性渐变(linearGradient)
ArkUI 的 linearGradient 属性可以为组件创建渐变背景。KPI 卡片使用从上到下的线性渐变:
.linearGradient({
direction: GradientDirection.Bottom, // 渐变方向:从上到下
colors: [
[card.gradientStart, 0], // 起始颜色,位置 0%
[card.gradientEnd, 1] // 结束颜色,位置 100%
]
})
GradientDirection 枚举:
| 值 | 说明 |
|---|---|
Left |
从右到左渐变 |
Right |
从左到右渐变 |
Top |
从下到上渐变 |
Bottom |
从上到下渐变 |
LeftTop |
从右下到左上渐变 |
LeftBottom |
从右上到左下渐变 |
RightTop |
从左下到右上渐变 |
RightBottom |
从左上到右下渐变 |
颜色的 colors 数组接受 [颜色值, 位置] 的元组,位置取值范围 0~1,表示颜色在渐变路径上的百分比位置。
四张卡片使用了不同的渐变组合:
| 卡片 | 起始色 | 结束色 | 视觉感受 |
|---|---|---|---|
| 总用户数 | #4A90D9 |
#357ABD |
冷静专业的蓝色 |
| 总收入 | #27AE60 |
#1E8449 |
增长财富的绿色 |
| 活跃用户 | #E67E22 |
#CA6F1E |
活力充沛的橙色 |
| 转化率 | #8E44AD |
#6C3483 |
高品质的紫色 |
4.2 阴影(shadow)
shadow 属性为卡片增加立体层次感:
.shadow({
radius: 8, // 模糊半径 (vp)
color: '#33000000', // 阴影颜色 (含透明度)
offsetX: 0, // 水平偏移
offsetY: 4 // 垂直偏移(向下投射)
})
阴影参数的视觉影响:
- radius(模糊半径):值越大,阴影边缘越柔和、扩散范围越大。
radius: 8产生适中的柔和阴影,适合卡片类组件。更大的值如 16 会更接近"光晕"效果。 - color(阴影颜色):使用包含透明度(Alpha)的十六进制颜色值。
#33000000表示完全黑色但透明度为 20%(0x33/0xFF ≈ 0.2),叠加在深色背景上不会显得突兀。浅色主题下可以使用#1A000000降低强度。 - offsetX / offsetY(偏移量):模拟光源方向。
offsetY: 4表示阴影向下偏移 4vp,模拟光源从上方照射的效果,这是最自然的阴影方向。
欢迎横幅使用了更大的阴影半径:
.shadow({
radius: 12,
color: '#33000000',
offsetX: 0,
offsetY: 4
})
更大的 radius(12 vs 8)让横幅的阴影更柔和、扩散更广,从视觉上突出其"最上层"的层次感。
4.3 圆角(borderRadius)
卡片圆角统一使用 borderRadius: 16,营造现代、友好的视觉风格。订单列表表头使用了不对称圆角:
// 表头:仅上方圆角
.borderRadius({
topLeft: 12, topRight: 12
})
// 表体下方无圆角,因为卡片整体已经有了 borderRadius: 16
// 表头和表体组合成一个完整的圆角卡片
这种"表头圆角 + 表体无下圆角 + 外层卡片圆角"的技巧,在保持整体圆角卡片外观的同时,让表头和表体之间的分隔线更自然。
4.4 Emoji 图标方案
Dashboard 中的图标全部使用 Emoji 字符,而不是图片资源或字体图标:
// 顶部导航 Emoji
Text('🔔').fontSize(20) // 通知铃铛
// 统计标签 Emoji
this.buildStatChip('📦', '本月订单', '326', '#4A90D9')
this.buildStatChip('👤', '新增用户', '1,247', '#27AE60')
this.buildStatChip('⭐', '好评率', '98.6%', '#E67E22')
// KPI 卡片 Emoji
icon: '👥', // 总用户数
icon: '💰', // 总收入
icon: '📊', // 活跃用户
icon: '🎯', // 转化率
Emoji 作为图标的优势:
- 无需额外资源文件,降低项目体积
- 跨平台一致性高(Emoji 由系统渲染)
- 开发效率高,改图标只需改一个字符
- 支持
fontSize控制大小,使用灵活
劣势:在低版本设备上 Emoji 渲染可能不一致,且颜色不可控。正式生产环境建议替换为矢量图标或图片资源。
五、状态管理与数据类型设计
5.1 接口定义
Dashboard 定义了三种数据结构,分别对应不同的卡片类型:
/** KPI 指标卡片数据 */
interface KpiCardData {
title: string; // 指标名称(如 "总用户数")
value: string; // 数值(如 "12,846")
change: string; // 变化率(如 "+12.5%")
trend: 'up' | 'down'; // 趋势方向,字符串字面量联合类型
icon: string; // 图标 Emoji
gradientStart: string; // 渐变起始色
gradientEnd: string; // 渐变结束色
}
/** 图表卡片数据 */
interface ChartCardData {
title: string; // 图表标题
type: string; // 图表类型描述(如 "折线图")
subTitle: string; // 副标题/数据概览
period: string; // 时间周期(如 "近30天")
color: string; // 主题色
}
/** 订单数据 */
interface OrderData {
id: string; // 订单号
customer: string; // 客户名
product: string; // 商品
amount: string; // 金额
status: string; // 状态文本(如 "已完成")
statusColor: string; // 状态颜色(如 "#27AE60")
}
设计要点:
-
trend: 'up' | 'down'使用 TypeScript 字符串字面量联合类型,比 boolean 的可读性更好。如果使用isUp: boolean,在分支判断时需要额外记住 true 代表上升、false 代表下降。而trend === 'up'一目了然。 -
statusColor放在数据而非 UI 中:订单状态的颜色是业务数据的一部分,不是 UI 表现。将来如果从后端获取订单数据,状态颜色可以由后端控制(例如不同状态的订单用不同颜色高亮),因此将其作为数据字段而非 UI 硬编码。 -
所有数据字段都是字符串:
value和amount虽然是数值含义,但定义为字符串以保留格式化信息(如 “12,846” 包含千分位逗号,“¥86,942” 包含货币符号)。格式化在数据层完成,UI 层直接展示。
5.2 @State 的使用
Dashboard 中的 @State 变量主要用于静态数据的展示:
@Entry
@Component
struct DashboardDemo {
@State kpiCards: KpiCardData[] = [ /* 4 张卡片数据 */ ];
@State chartCards: ChartCardData[] = [ /* 2 张图表数据 */ ];
@State recentOrders: OrderData[] = [ /* 4 条订单数据 */ ];
@State currentDate: string = '';
aboutToAppear(): void {
// 初始化日期
const now: Date = new Date();
// ... 格式化日期
this.currentDate = `${year}年${month}月${day}日 星期${weekday}`;
}
}
关于 @State 在展示页场景下的思考:
虽然 Dashboard 当前是纯静态展示页面(没有用户交互改变数据),但使用 @State 仍然有重要意义:
- 为后续动态化预留接口:当仪表盘接入真实数据源时,只需修改
@State变量,UI 会自动更新,无需修改 build() 方法。 - 生命周期钩子支持:
aboutToAppear()在组件初始化时自动执行,适合做数据初始化。 - 保持与动态页面的一致性:项目中其他页面(聊天界面、日历)都使用
@State,统一风格降低认知负担。
aboutToAppear() 生命周期:
这是 ArkUI 组件的生命周期之一,在组件即将挂载到视图树时调用。执行时机在 build() 方法之前,适合做数据准备。与之对应的还有 aboutToDisappear(),在组件即将销毁时调用,适合做资源清理。
5.3 日期格式化实现
currentDate 在 aboutToAppear() 中初始化:
aboutToAppear(): void {
const now: Date = new Date();
const year: number = now.getFullYear();
const month: number = now.getMonth() + 1; // getMonth() 返回 0-11
const day: number = now.getDate();
const weekdays: string[] = ['日', '一', '二', '三', '四', '五', '六'];
const weekday: string = weekdays[now.getDay()]; // getDay() 返回 0-6
this.currentDate = `${year}年${month}月${day}日 星期${weekday}`;
}
这里用到的 JavaScript Date API 技巧:
getMonth()返回 0~11,因此需要+ 1才能得到实际的月份getDay()返回 0(周日)~6(周六),刚好对应weekdays数组的索引- 模板字符串
${}是 ES6 语法,ArkTS 完全支持
六、组件化构建 @Builder 详解
6.1 @Builder 的拆分原则
Dashboard 页面将 UI 拆分为 7 个 @Builder 方法:
| @Builder 方法 | 用途 | 参数 |
|---|---|---|
buildHeader() |
顶部导航栏 | 无 |
buildWelcomeBanner() |
欢迎横幅卡片 | 无 |
buildStatChip(icon, label, value, color) |
概要统计小标签 | 4个参数 |
buildSectionTitle(title, action) |
区域标题生成器 | 2个参数 |
buildKpiGrid() |
KPI 指标卡片网格 | 无 |
buildChartGrid() |
图表卡片网格 | 无 |
buildOrderList() |
订单列表 | 无 |
拆分的依据:
- 功能独立性:每个 @Builder 对应一个独立的 UI 区块,有明确的视觉边界
- 参数化:
buildStatChip和buildSectionTitle接受参数,可以在不同位置复用 - 逻辑复杂度:
buildKpiGrid和buildChartGrid内部包含 Grid + ForEach 循环,不适合直接放在主 build() 中 - 可读性:主
build()方法只有 30 行左右,充当"目录"角色
6.2 @Builder 带参数的定义与调用
buildStatChip 是一个带参数的 @Builder:
@Builder
buildStatChip(icon: string, label: string, value: string, color: string): void {
Row() {
Text(icon)
.fontSize(16)
.margin({ right: 8 })
Column() {
Text(value)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Text(label)
.fontSize(11)
.fontColor('#8899AA')
}
.alignItems(HorizontalAlign.Start)
}
.layoutWeight(1) // ★★★ 三等分宽度 ★★★
.padding({ top: 8, bottom: 8 })
.justifyContent(FlexAlign.Center)
}
在欢迎横幅中调用三次:
Row() {
this.buildStatChip('📦', '本月订单', '326', '#4A90D9')
this.buildStatChip('👤', '新增用户', '1,247', '#27AE60')
this.buildStatChip('⭐', '好评率', '98.6%', '#E67E22')
}
.width('100%')
.margin({ top: 16 })
layoutWeight(1) 的妙用:三个 buildStatChip 都设置了 layoutWeight(1),外层的 Row 按照 1:1:1 的比例等分宽度。如果将来要增加第四个统计标签,只需添加一行调用,不用修改任何布局代码。
6.3 区域标题生成器
buildSectionTitle 是一个简单的标题 + 操作按钮生成器:
@Builder
buildSectionTitle(title: string, action: string): void {
Row() {
Text(title)
.fontSize(17)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
Blank() // ★★★ 弹性占位,把操作按钮推到右侧 ★★★
Text(action)
.fontSize(13)
.fontColor('#4A90D9')
}
.width('100%')
.margin({ top: 16, bottom: 12 })
}
在页面中被调用三次:
this.buildSectionTitle('📈 核心指标', '导出报告')
this.buildSectionTitle('📊 数据分析', '全部报表')
this.buildSectionTitle('📋 最近订单', '查看全部')
这种模式的好处是统一性——所有区域标题的外观和间距完全一致,修改时只需改一处。
七、图表卡片中的模拟可视化
7.1 用 Column 模拟柱状图
Dashboard 的图表卡片区域使用 Column 组件模拟了简单的柱状图:
// ★★★ 图表占位区域:用简化图形模拟趋势线 ★★★
Column() {
Row() {
// 用不同高度的柱状条模拟图表
ForEach([20, 35, 25, 45, 30, 50], (height: number) => {
Column() {
// 透明占位
}
.width(12)
.height(height) // 每个柱子的高度不同
.backgroundColor(chart.color) // 使用卡片的主题色
.borderRadius(6) // 圆角柱状
.margin({ left: 3, right: 3 })
.opacity(0.6) // 半透明,更柔和
.alignSelf(ItemAlign.End) // ★★★ 底部对齐 ★★★
})
}
.width('100%')
.height(60)
.alignItems(VerticalAlign.Bottom) // ★★★ Row 底部对齐 ★★★
.justifyContent(FlexAlign.SpaceEvenly) // ★★★ 均匀分布 ★★★
.margin({ top: 12 })
}
模拟柱状图的关键技术:
-
alignSelf(ItemAlign.End):每个柱子在 Row 中底部对齐。柱子本身是Column()容器(内部透明占位),其高度通过.height(height)设置。底部对齐后,柱子的顶部高度各不相同,形成柱状图效果。 -
alignItems(VerticalAlign.Bottom):Row 内的所有柱状条在垂直方向底部对齐,与alignSelf(ItemAlign.End)配合确保一致。 -
justifyContent(FlexAlign.SpaceEvenly):柱状条在水平方向上均匀分布,包括与容器边缘的间距也相等,形成规整的视觉排列。 -
opacity(0.6):半透明效果让柱状条看起来更柔和,不会与卡片内容争夺视觉焦点。 -
高度数组
[20, 35, 25, 45, 30, 50]:模拟了一个上升趋势的数据序列,视觉上呈现从左到右的爬升感。
7.2 生产环境中的图表方案
本文中的柱状图模拟仅用于演示 Grid + 卡片的布局能力。在生产环境中,实现真正的图表可视化有以下方案:
- 使用 Canvas 组件:ArkUI 提供了
Canvas组件支持 2D 自定义绘制,可以实现任意类型的图表 - 集成第三方图表库:通过 ohpm 安装图表库(如 echarts 的鸿蒙适配版)
- 服务端渲染图表图片:由后端生成图表图片,前端用
Image组件加载
八、订单列表中的表格布局技巧
8.1 layoutWeight 实现列宽比例
订单列表的每一行使用 layoutWeight 控制各列的宽度比例:
Row() {
Text('订单号').layoutWeight(1.5) // 占 1.5 份
Text('客户').layoutWeight(1) // 占 1 份
Text('商品').layoutWeight(1.5) // 占 1.5 份
Text('金额').layoutWeight(0.8) // 占 0.8 份
Text('状态').layoutWeight(0.8) // 占 0.8 份
}
各列宽度比例:1.5 : 1.0 : 1.5 : 0.8 : 0.8 = 订单号 > 商品 > 客户 > 金额 = 状态
这种比例设计的考虑:
- “订单号” 和 “商品” 通常较长,分配更多宽度
- “金额” 和 “状态” 内容较短,分配较少宽度
- “客户” 名称长短不一,取中间值
8.2 彩色状态标签
订单状态使用带背景色的 Text 组件模拟标签:
Text(order.status)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor(order.statusColor) // 数据驱动颜色
.borderRadius(10) // 胶囊圆角
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.layoutWeight(0.8)
.textAlign(TextAlign.Center)
三种状态对应三种颜色:
| 状态 | 颜色 | 十六进制 |
|---|---|---|
| 已完成 | 绿色 | #27AE60 |
| 处理中 | 橙色 | #E67E22 |
| 待发货 | 蓝色 | #4A90D9 |
颜色值存储在数据中而非硬编码在 UI 中,意味着未来从后端获取数据时,后端可以灵活控制每个订单的状态颜色。
8.3 条件分隔线
订单行之间的分隔线使用条件渲染,最后一行不显示:
if (order.id !== this.recentOrders[this.recentOrders.length - 1].id) {
Divider().height(1).color('#2A3A4A').width('100%').margin({ left: 16 })
}
判断逻辑:当前订单的 ID 不等于数组中最后一个订单的 ID 时渲染分隔线。这确保了最后一条订单下方不会有多余的分隔线,视觉上更干净。
九、项目工程配置详解
9.1 build-profile.json5 构建设置
项目根目录的 build-profile.json5 定义了工程级的构建配置:
{
"app": {
"signingConfigs": [],
"products": [
{
"name": "default",
"targetSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS",
"buildOption": {
"strictMode": {
"caseSensitiveCheck": true,
"useNormalizedOHMUrl": true
}
}
}
],
"buildModeSet": [
{ "name": "debug" },
{ "name": "release" }
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{ "name": "default", "applyToProducts": ["default"] }
]
}
]
}
关键配置项说明:
targetSdkVersion: "6.1.1(24)"和compatibleSdkVersion: "6.1.1(24)":目标 SDK 和兼容 SDK 均为 6.1.1,API 等级 24。这是 HarmonyOS NEXT 的关键版本标识。runtimeOS: "HarmonyOS":明确指定运行操作系统为鸿蒙。strictMode:开启严格模式,caseSensitiveCheck确保文件名大小写匹配,useNormalizedOHMUrl确保 OHM(OpenHarmony Module)URL 规范化。
9.2 main_pages.json 页面路由
entry/src/main/resources/base/profile/main_pages.json 配置了应用的页面路由:
{
"src": [
"pages/DashboardDemo"
]
}
每个 src 条目对应 pages/ 目录下的一个 .ets 文件,路径不需要 .ets 后缀。EntryAbility 通过 loadContent('pages/DashboardDemo') 加载页面。
9.3 EntryAbility 启动流程
EntryAbility 是应用启动的入口:
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/DashboardDemo', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'EnglishApp',
'Failed to load content: %{public}s', JSON.stringify(err));
return;
}
hilog.info(DOMAIN, 'EnglishApp', 'Succeeded in loading content.');
});
}
}
windowStage.loadContent 接受两个参数:
- 页面路径:字符串,对应
main_pages.json中的注册路径 - 回调函数:
(err: BusinessError) => void,加载完成后调用。err.code为 0 表示成功,非零为错误码
hilog 日志格式说明:
hilog.error(domain, tag, format, ...args);
domain:十六进制整数,开发者自定义标识领域tag:字符串标签,用于过滤日志format:格式化字符串,%{public}s表示公开的字符串参数(隐私模式下%{private}s表示敏感信息会被脱敏)
十、性能优化与最佳实践
10.1 Grid 的渲染性能
Grid 组件在渲染大量 GridItem 时具有较好的性能表现,因为 Grid 会在内部进行布局优化。但在 Dashboard 场景中,KPI 网格只有 4 个 GridItem,图表网格只有 2 个,属于极轻量级使用,不需要特殊的性能优化。
需要关注的性能场景(当 GridItem 数量 > 50 时):
- 避免复杂的 GridItem 嵌套:每层嵌套都会增加布局计算时间
- 使用 LazyForEach 替代 ForEach:大数据列表推荐使用懒加载
- 避免在 GridItem 中使用频繁变化的动画
10.2 @Builder 的复用与复用限制
@Builder 方法可以在同一个组件内复用,但不支持跨组件复用。如果需要跨组件复用 UI 逻辑,有以下方案:
- 全局 @Builder 函数(API 24 支持):
// 全局 Builder 函数
@Builder function GlobalStatChip(icon: string, value: string, label: string) {
Row() {
Text(icon)
Column() {
Text(value)
Text(label)
}
}
}
// 在任意组件中直接使用函数名调用
@Component
struct SomeComponent {
build() {
Column() {
GlobalStatChip('📦', '326', '本月订单')
}
}
}
- 自定义组件(推荐方案):
将频繁复用的 UI 片段抽取为独立的 @Component struct:
@Component
struct StatChip {
private icon: string = ''
private value: string = ''
private label: string = ''
build() {
Row() {
Text(this.icon).fontSize(16).margin({ right: 8 })
Column() {
Text(this.value).fontSize(16)
.fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text(this.label).fontSize(11).fontColor('#8899AA')
}
.alignItems(HorizontalAlign.Start)
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
}
// 使用
StatChip({ icon: '📦', value: '326', label: '本月订单' })
自定义组件相比于 @Builder 的优势在于:独立生命周期、独立的 @State 状态、跨组件复用。
10.3 深色主题的色彩系统设计
Dashboard 的深色主题基于以下原则设计:
原则一:层次分明
底层背景: #0F1923 (最暗)
卡片表面: #1E2D3D (次暗)
导航栏: #1A2A3A (介于之间)
文字主色: #FFFFFF (最亮)
文字次要: #B0C4DE / #8899AA (半亮)
通过 5 个灰度层级建立清晰的视觉层次,用户的视线会自然地被亮色内容吸引。
原则二:色彩克制
深色主题下,大面积使用高饱和颜色会产生视觉疲劳。Dashboard 只在以下三个位置使用颜色:
- KPI 卡片的渐变背景(4 种颜色,各占 25% 卡片面积)
- 强调按钮/链接(蓝色 #4A90D9)
- 涨跌/状态指示(绿色/红色/橙色/蓝色小面积标签)
原则三:透明度辅助
阴影颜色使用 #33000000(20% 透明度黑色),柱状图使用 opacity(0.6),在深色背景上产生微妙的层次变化而不增加新的色相。
10.4 常见陷阱与解决方案
陷阱一:Grid 内容不显示
Grid 必须同时满足以下条件才能显示内容:
columnsTemplate格式正确且有效- Grid 本身有有效的宽度(
width('100%')或固定宽度) - Grid 或 GridItem 有有效的高度
解决方案:
// ✅ 正确的 Grid 配置
Grid() {
GridItem() { /* ... */ }
}
.columnsTemplate('1fr 1fr') // 列模板有效
.width('100%') // Grid 有宽度
.height(140) // Grid 有高度
陷阱二:GridItem 内容被裁剪
当 GridItem 的内容超出 GridItem 的大小时,内容会被裁剪。确保 GridItem 的行高足够容纳内容。
解决方案:使用 constraintSize 限制内容的最大尺寸,或增加 rowsTemplate 中的行高比例。
陷阱三:@State 数组更新不触发 UI
// ❌ 不触发 UI 更新
this.kpiCards[0].value = '15,000'
// ✅ 触发 UI 更新
this.kpiCards = [...this.kpiCards] // 创建新数组引用
// 然后修改 kpiCards[0].value
对于嵌套对象的修改,需要使用展开运算符创建新的对象层级。
十一、总结与展望
11.1 技术要点回顾
本文通过一个完整的 Dashboard 仪表盘页面,系统性地讲解了 HarmonyOS NEXT ArkUI 中的以下核心技术:
布局方面:
- Grid 组件实现 2 列等宽卡片网格
- columnsTemplate 和 rowsTemplate 的 fr 单位机制
- Scroll + layoutWeight 实现可滚动内容区域
- Row + Blank 实现两端对齐
视觉设计方面:
- linearGradient 实现卡片渐变背景
- shadow 属性为卡片增加立体感
- borderRadius 圆角设计
- 深色主题的配色体系
组件化方面:
- @Builder 装饰器拆分 UI 模块
- 带参数的 @Builder 实现复用
- build() 作为 UI 目录
数据管理方面:
- interface 定义数据结构
- @State 管理响应式数据
- aboutToAppear 生命周期初始化
11.2 从 Dashboard 到生产环境
本文的 Dashboard 示例展示了核心布局能力,但从示例到生产环境还需补充以下能力:
- 数据源接入:使用
@State+ 异步请求(http模块或@ohos.net.http)从后端 API 获取真实数据 - 下拉刷新:使用
Refresh组件包裹内容区,实现下拉刷新 - 图表可视化:集成 Canvas 绘制或第三方图表库
- 交互反馈:点击卡片跳转详情页、手势滑动删除订单等
- 响应式适配:根据屏幕宽度动态调整 Grid 列数(平板端可改为 3 列或 4 列)
11.3 ArkUI 布局的未来
随着 HarmonyOS NEXT 的持续迭代,ArkUI 的布局能力在不断增强。从 API 24 开始,以下特性值得关注:
- 自适应布局 API:
@ohos.adaptive模块提供屏幕断点适配能力 - 自定义布局:通过
Layout接口实现完全自定义的布局算法 - 布局动画:GridItem 的增删自动伴随动画过渡
- 跨端布局一致性:同一套 ArkUI 代码在手机、平板、折叠屏、车机上保持一致的布局表现
对于正在进入鸿蒙生态的开发者而言,现在深入学习 ArkUI 的布局系统,将是在鸿蒙原生应用开发领域建立技术优势的最佳时机。
附录:完整代码与构建指南
附录 A:DashboardDemo.ets 结构总览
文件: entry/src/main/ets/pages/DashboardDemo.ets
语言: ArkTS (HarmonyOS NEXT 6.1.1 / API 24)
行数: 686 行
├── 接口定义 (3 个 interface)
│ ├── KpiCardData — KPI 指标卡片数据
│ ├── ChartCardData — 图表卡片数据
│ └── OrderData — 订单数据
│
├── struct DashboardDemo (@Entry @Component)
│ ├── @State 变量 (4 个)
│ │ ├── kpiCards — KPI 卡片数据数组
│ │ ├── chartCards — 图表卡片数据数组
│ │ ├── recentOrders — 订单数据数组
│ │ └── currentDate — 当前日期字符串
│ │
│ ├── 生命周期
│ │ └── aboutToAppear() — 日期初始化
│ │
│ ├── build() — 主构建方法
│ │ ├── buildHeader()
│ │ ├── Scroll()
│ │ │ └── Column()
│ │ │ ├── buildWelcomeBanner()
│ │ │ ├── buildKpiGrid() ★ Grid KPI 卡片
│ │ │ ├── buildChartGrid() ★ Grid 图表卡片
│ │ │ └── buildOrderList()
│ │ └── Blank()
│ │
│ ├── @Builder buildHeader()
│ ├── @Builder buildWelcomeBanner()
│ ├── @Builder buildStatChip(icon, label, value, color)
│ ├── @Builder buildSectionTitle(title, action)
│ ├── @Builder buildKpiGrid() ★★★ 核心 Grid
│ ├── @Builder buildChartGrid() ★★★ 核心 Grid
│ └── @Builder buildOrderList()
附录 B:构建与运行命令
# 工程根目录执行
# PreBuild 快速验证
hvigorw PreBuildApp --no-daemon
# Debug 构建
hvigorw assembleApp --mode debug --no-daemon
# Release 构建(需配置签名)
hvigorw assembleApp --mode release --no-daemon
附录 C:Grid 组件 API 速查
| 属性 | 类型 | 说明 | 示例 |
|---|---|---|---|
columnsTemplate |
string | 列模板,fr 单位 | '1fr 1fr 1fr' 三列等宽 |
rowsTemplate |
string | 行模板,fr 单位 | '1fr 1fr' 两行等高 |
columnsGap |
number | 列间距 (vp) | 12 |
rowsGap |
number | 行间距 (vp) | 12 |
scrollBarState |
BarState | 滚动条状态 | BarState.Off |
editMode |
boolean | 编辑模式 | false |
layoutWeight |
number | 弹性权重(在 Row/Column 中) | 1 |
附录 D:卡片视觉属性速查
| 属性 | 用途 | KPI 卡片 | 图表卡片 | 订单卡片 |
|---|---|---|---|---|
backgroundColor |
背景色 | linearGradient 渐变 | #1E2D3D |
#1E2D3D |
borderRadius |
圆角 | 16 | 16 | 16 |
padding |
内边距 | 18 | 18 | 0(由子项决定) |
shadow |
阴影 | radius:8 | radius:8 | radius:8 |
.height() |
高度 | 140 | 180 | 自动撑开 |
更多推荐




所有评论(0)