HarmonyOS NEXT 企业级记账APP:搭建全局主题与 Design Token
搭建全局主题与 Design Token
本文是《HarmonyOS NEXT 企业级开发实战:30篇打造智能记账APP》系列的第 03 篇,对应 Git Tag v0.0.3。承接第 02 篇的工程骨架,本篇设计 HarmonyLedger 的色彩体系、字号体系、间距体系,并封装深色模式支持,为后续所有页面提供统一的视觉基础。
前言
企业级应用的视觉一致性是 用户体验的基础。如果没有统一的主题规范,每个页面各写一套颜色、字号、间距,最终会演变成视觉灾难。Design Token 是业界主流的设计系统方案,通过将视觉要素抽象为可复用的 Token,实现主题的集中管理与一键切换。
本文将带你:
- 设计 HarmonyLedger 的色彩体系(收入绿/支出红/预算蓝/统计紫)
- 搭建字号体系与间距体系
- 用 ArkTS 常量类封装 Design Token
- 通过 AppStorage 实现深色模式动态切换
企业级核心原则:视觉要素必须 集中定义、统一引用、禁止硬编码。参考 Material Design Tokens 了解业界主流方案。
一、Design Token 概念
1.1 什么是 Design Token
Design Token 是设计系统的原子单元,用于存储视觉要素的值。一个 Token 通常包含:
| Token 属性 | 说明 | 示例 |
|---|---|---|
name |
Token 命名 | color.income.primary |
value |
Token 取值 | #34C759 |
description |
用途描述 | “收入主色,用于收入账单卡片” |
platform |
适用平台 | harmonyos |
1.2 Token 分层
HarmonyLedger 的 Token 分 三层:
Layer 1: Base Token(基础色板)
└ primary, secondary, accent, neutral 色系
Layer 2: Semantic Token(语义化 Token)
└ income, expense, budget, statistic
Layer 3: Component Token(组件 Token)
└ bill.card.background, bill.card.text.primary
设计要点:禁止组件直接引用 Base Token,必须通过 Semantic Token 中转。这样色板调整时只需改 Base Token,所有 Semantic Token 自动继承。
二、色彩体系设计
2.1 色彩语义规划
HarmonyLedger 采用 语义化色彩 方案,不同业务场景使用不同色系:
| 语义 | 色系 | 主色 | 浅色 | 应用场景 |
|---|---|---|---|---|
| 收入 | 绿色 | #34C759 |
#E8F8EE |
收入账单卡片、收入金额 |
| 支出 | 红色 | #FF3B30 |
#FFEBE9 |
支出账单卡片、支出金额 |
| 预算 | 蓝色 | #007AFF |
#E3F0FF |
预算进度条、预算提醒 |
| 统计 | 紫色 | #AF52DE |
#F5E8FB |
图表图例、统计标题 |
| 中性 | 灰色 | #8E8E93 |
#F2F2F7 |
背景、分割线、次要文字 |
2.2 颜色定义文件
// theme/Colors.ets - 色彩 Token 定义
export class AppColors {
// ===== Base Token:基础色板 =====
static readonly Green500: string = '#34C759';
static readonly Green100: string = '#E8F8EE';
static readonly Red500: string = '#FF3B30';
static readonly Red100: string = '#FFEBE9';
static readonly Blue500: string = '#007AFF';
static readonly Blue100: string = '#E3F0FF';
static readonly Purple500: string = '#AF52DE';
static readonly Purple100: string = '#F5E8FB';
// ===== Semantic Token:语义化色 =====
static readonly Income: string = AppColors.Green500;
static readonly IncomeLight: string = AppColors.Green100;
static readonly Expense: string = AppColors.Red500;
static readonly ExpenseLight: string = AppColors.Red100;
static readonly Budget: string = AppColors.Blue500;
static readonly BudgetLight: string = AppColors.Blue100;
static readonly Statistic: string = AppColors.Purple500;
static readonly StatisticLight: string = AppColors.Purple100;
// ===== 中性色(浅色模式) =====
static readonly Background: string = '#F2F2F7';
static readonly CardBackground: string = '#FFFFFF';
static readonly PrimaryText: string = '#1C1C1E';
static readonly SecondaryText: string = '#8E8E93';
static readonly Separator: string = '#E5E5EA';
static readonly Border: string = '#D1D1D6';
}
2.3 深色模式色板
// theme/DarkColors.ets - 深色模式色板
export class AppDarkColors {
// 语义色保持一致(保证品牌识别)
static readonly Income: string = '#30D158';
static readonly IncomeLight: string = '#1B2A1E';
static readonly Expense: string = '#FF453A';
static readonly ExpenseLight: string = '#2B1C1C';
static readonly Budget: string = '#0A84FF';
static readonly BudgetLight: string = '#0A1F2B';
static readonly Statistic: string = '#BF5AF2';
static readonly StatisticLight: string = '#1F0F2B';
// 中性色反转(深色模式)
static readonly Background: string = '#000000';
static readonly CardBackground: string = '#1C1C1E';
static readonly PrimaryText: string = '#FFFFFF';
static readonly SecondaryText: string = '#8E8E93';
static readonly Separator: string = '#38383A';
static readonly Border: string = '#545458';
}
深色模式原则:语义色保持品牌识别,中性色反转。参考 Apple Dark Mode Guidelines 了解业界主流实践。
三、字号与间距体系
3.1 字号体系
HarmonyLedger 采用 4 倍数 字号体系,共 6 个字号阶梯:
| Token | 字号 | 用途 | 示例 |
|---|---|---|---|
FontSizeXS |
12 | 辅助文字 | 时间戳、标签 |
FontSizeSM |
14 | 次要正文 | 备注、描述 |
FontSizeMD |
16 | 默认正文 | 账单备注 |
FontSizeLG |
18 | 标题 | 卡片标题 |
FontSizeXL |
22 | 页面标题 | 页面 Header |
FontSizeXXL |
28 | 大数字 | 金额汇总 |
// theme/Typography.ets - 字号 Token
export class AppFontSize {
static readonly XS: number = 12;
static readonly SM: number = 14;
static readonly MD: number = 16;
static readonly LG: number = 18;
static readonly XL: number = 22;
static readonly XXL: number = 28;
static readonly Display: number = 36; // 首页大数字
}
3.2 间距体系
同样采用 4 倍数 间距体系:
| Token | 间距 | 用途 |
|---|---|---|
SpaceXS |
4 | 细微间距(图标与文字) |
SpaceSM |
8 | 小间距(卡片内元素) |
SpaceMD |
16 | 中间距(卡片 Padding) |
SpaceLG |
20 | 大间距(卡片间) |
SpaceXL |
24 | 页面边距 |
SpaceXXL |
32 | 区块间距 |
// theme/Spacing.ets - 间距 Token
export class AppSpace {
static readonly XS: number = 4;
static readonly SM: number = 8;
static readonly MD: number = 16;
static readonly LG: number = 20;
static readonly XL: number = 24;
static readonly XXL: number = 32;
static readonly PagePadding: number = 20; // 页面统一 Padding
static readonly CardPadding: number = 16; // 卡片统一 Padding
static readonly CardRadius: number = 16; // 卡片统一圆角
}
3.3 字重体系
// theme/Typography.ets - 字重 Token
export class AppFontWeight {
static readonly Regular: FontWeight = FontWeight.Regular;
static readonly Medium: FontWeight = FontWeight.Medium;
static readonly Bold: FontWeight = FontWeight.Bold;
}
设计要点:字号、间距、字重统一用 Token 管理,禁止在页面里硬编码
fontSize(18)这样的数字。
四、主题管理器封装
4.1 ThemeManager.ets
通过 AppStorage 实现深色模式动态切换:
// theme/ThemeManager.ets
import { AppStorage } from '@kit.ArkUI';
import { AppColors } from './Colors';
import { AppDarkColors } from './DarkColors';
export enum ThemeMode {
LIGHT = 'light',
DARK = 'dark',
AUTO = 'auto' // 跟随系统
}
export class ThemeManager {
private static readonly KEY_THEME_MODE = 'theme_mode';
private static currentMode: ThemeMode = ThemeMode.AUTO;
/** 初始化主题(从 Preferences 读取) */
static init(mode: ThemeMode): void {
this.currentMode = mode;
AppStorage.setOrCreate(this.KEY_THEME_MODE, mode);
this.applyTheme(mode);
}
/** 切换主题 */
static switch(mode: ThemeMode): void {
this.currentMode = mode;
AppStorage.set(this.KEY_THEME_MODE, mode);
this.applyTheme(mode);
}
/** 应用主题到全局 */
private static applyTheme(mode: ThemeMode): void {
if (mode === ThemeMode.DARK) {
this.applyDarkColors();
} else {
this.applyLightColors();
}
}
private static applyLightColors(): void {
AppStorage.setOrCreate('color.background', AppColors.Background);
AppStorage.setOrCreate('color.card', AppColors.CardBackground);
AppStorage.setOrCreate('color.text.primary', AppColors.PrimaryText);
AppStorage.setOrCreate('color.text.secondary', AppColors.SecondaryText);
AppStorage.setOrCreate('color.separator', AppColors.Separator);
}
private static applyDarkColors(): void {
AppStorage.setOrCreate('color.background', AppDarkColors.Background);
AppStorage.setOrCreate('color.card', AppDarkColors.CardBackground);
AppStorage.setOrCreate('color.text.primary', AppDarkColors.PrimaryText);
AppStorage.setOrCreate('color.text.secondary', AppDarkColors.SecondaryText);
AppStorage.setOrCreate('color.separator', AppDarkColors.Separator);
}
static getCurrentMode(): ThemeMode {
return this.currentMode;
}
}
4.2 在 Ability 中初始化主题
// entryability/EntryAbility.ets
import { ThemeManager } from '../theme/ThemeManager';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化主题(默认跟随系统)
ThemeManager.init(ThemeMode.AUTO);
}
}
关键技术:
AppStorage.setOrCreate用于写入全局响应式数据,任何@StorageLink绑定会自动刷新 UI。这是 ArkUI 状态管理的核心机制。
五、主题使用示例
5.1 在组件中引用主题色
// components/BillCard.ets
import { AppColors } from '../theme/Colors';
import { AppFontSize } from '../theme/Typography';
import { AppSpace } from '../theme/Spacing';
@Component
export struct BillCard {
@Prop money: number;
@Prop type: string; // 'income' | 'expense'
build() {
Column() {
Text(`¥${this.money}`)
.fontSize(AppFontSize.XL)
.fontColor(this.type === 'income' ? AppColors.Income : AppColors.Expense)
.fontWeight(FontWeight.Bold)
}
.padding(AppSpace.CardPadding)
.backgroundColor(AppColors.CardBackground)
.borderRadius(AppSpace.CardRadius)
}
}
5.2 响应式深色模式
// pages/HomeView.ets
@Entry
@Component
struct HomeView {
@StorageLink('color.background') bgColor: string = '#F2F2F7';
@StorageLink('color.text.primary') textColor: string = '#1C1C1E';
build() {
Column() {
Text('HarmonyLedger 鸿蒙记账')
.fontColor(this.textColor)
}
.width('100%')
.height('100%')
.backgroundColor(this.bgColor)
}
}
深色模式切换:调用
ThemeManager.switch(ThemeMode.DARK)后,所有@StorageLink绑定的颜色会自动刷新,UI 即时响应。
六、主题常量集中管理
6.1 Theme Index 文件
// theme/index.ets - 主题统一导出
export { AppColors } from './Colors';
export { AppDarkColors } from './DarkColors';
export { AppFontSize } from './Typography';
export { AppFontWeight } from './Typography';
export { AppSpace } from './Spacing';
export { ThemeManager, ThemeMode } from './ThemeManager';
6.2 资源文件中的颜色定义
除了 ArkTS 常量,还需在 resources/base/element/color.json 定义静态色:
{
"color": [
{ "name": "start_window_background", "value": "#F2F2F7" },
{ "name": "icon_background", "value": "#FFFFFF" }
]
}
| 资源色 | 用途 | 定义位置 |
|---|---|---|
start_window_background |
启动屏背景 | resources/base/element/color.json |
icon_background |
应用图标背景 | resources/base/element/color.json |
| 动态主题色 | UI 组件 | theme/Colors.ets(ArkTS 常量) |
七、最佳实践
7.1 禁止硬编码颜色
// ❌ 错误:硬编码颜色
Text('¥100')
.fontColor('#FF3B30')
// ✅ 正确:引用 Token
Text('¥100')
.fontColor(AppColors.Expense)
为什么禁止硬编码?
- 深色模式失效:硬编码颜色无法响应主题切换
- 维护困难:色板调整需全局搜索替换
- 品牌不一致:多人协作时颜色可能跑偏
7.2 语义色与组件色分层
组件禁止直接引用 Base Token:
❌ Text().fontColor(AppColors.Red500)
✅ Text().fontColor(AppColors.Expense)
只有 Semantic Token 可以引用 Base Token:
static readonly Expense = AppColors.Red500;
7.3 深色模式测试矩阵
| 测试场景 | 浅色模式 | 深色模式 |
|---|---|---|
| 首页背景 | #F2F2F7 |
#000000 |
| 卡片背景 | #FFFFFF |
#1C1C1E |
| 主文字 | #1C1C1E |
#FFFFFF |
| 收入金额 | #34C759 |
#30D158 |
| 支出金额 | #FF3B30 |
#FF453A |
八、运行验证
8.1 编译检查
hvigorw assembleHap --mode module -p product=default
8.2 视觉验证
- 运行应用,首页背景应为
#F2F2F7(浅灰) - 调用
ThemeManager.switch(ThemeMode.DARK),首页应即时变为#000000(纯黑) - 文字颜色应同步切换为白色


九、常见问题
9.1 AppStorage 不刷新
| 现象 | 原因 | 解决 |
|---|---|---|
@StorageLink 不响应 |
未调用 setOrCreate 初始化 |
Ability onCreate 中先 ThemeManager.init |
| 切换无效果 | ThemeManager.switch 未调用 AppStorage.set |
检查 applyTheme 方法调用链 |
9.2 颜色显示错误
// 错误:颜色字符串格式不对
.backgroundColor('rgb(255, 0, 0)') // ❌ ArkUI 不支持 rgb()
// 正确:统一用 hex
.backgroundColor('#FF0000') // ✅
9.3 深色模式跟随系统
// 通过 ConfigurationConstant 读取系统主题
import { ConfigurationConstant } from '@kit.AbilityKit';
onConfigurationUpdate(config: ConfigurationConstant): void {
const colorMode = config.colorMode;
if (colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK) {
ThemeManager.switch(ThemeMode.DARK);
} else {
ThemeManager.switch(ThemeMode.LIGHT);
}
}
十、Git 提交
10.1 Commit Message
git add .
git commit -m "feat(theme): 搭建全局主题与 Design Token 系统
- 设计色彩体系(收入绿/支出红/预算蓝/统计紫)
- 定义字号、间距、字重 Token
- 封装 ThemeManager 支持深浅模式切换
- 通过 AppStorage 实现响应式主题
- 新增 theme/ 目录集中管理视觉 Token"
10.2 CHANGELOG
## [v0.0.3] - 2026-07-27
### Added
- theme/Colors.ets:浅色模式色板(Base + Semantic Token)
- theme/DarkColors.ets:深色模式色板
- theme/Typography.ets:字号、字重 Token
- theme/Spacing.ets:间距 Token
- theme/ThemeManager.ets:主题管理器,支持 AppStorage 响应式切换
- EntryAbility 初始化主题
总结
本文完整介绍了 HarmonyLedger 的 Design Token 体系搭建,涵盖色彩、字号、间距、字重四大视觉要素。通过本篇你可以:
- 理解 Design Token 的三层架构(Base / Semantic / Component)
- 设计语义化色彩体系(收入绿/支出红/预算蓝/统计紫)
- 用 ArkTS 常量类封装 Token,禁止硬编码
- 通过 AppStorage + ThemeManager 实现深色模式动态切换
- 在组件中正确引用 Token 并响应主题变化
下一篇预告:《实现底部 Tab 导航》将使用 ArkUI 的 Tabs 组件搭建首页、统计、预算、我的四个底导航 Tab,并封装 AppTabBar 通用组件。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源
- 本篇源码:GitHub Tag v0.0.3
- Material Design Tokens:m3.material.io
- Apple Dark Mode 指南:developer.apple.com/dark-mode
- ArkUI 状态管理:State Management
- AppStorage 文档:AppStorage API
- 鸿蒙深色模式适配:Dark Mode Adaptation
- Design Token 业界实践:Design Tokens W3C
更多推荐



所有评论(0)