一、技术前言

在万物智联的时代浪潮中,HarmonyOS 以其分布式软总线、一次开发多端部署的核心理念,正在重塑移动应用开发的技术格局。而 ArkUI 作为 HarmonyOS 官方主推的声明式 UI 开发框架,凭借 ArkTS 语言的类型安全特性与声明式语法范式,为开发者提供了兼具表达力与性能优势的 UI 构建能力。

ArkUI 框架的核心优势体现在以下几个层面。其一,声明式开发范式:开发者通过描述 UI “是什么"而非"怎么做”,大幅降低了 UI 逻辑的心智负担。@Component 装饰器标记自定义组件,build() 方法内以链式调用方式描述视图结构,代码即文档,可读性极强。其二,状态驱动视图更新@State@Observed@Link 等装饰器构成了精细化的状态管理体系,数据变化自动驱动 UI 刷新,开发者无需手动操作 DOM 或调用 set 方法。其三,内置组件丰富:从基础的 Text、Button、Image,到容器类的 Column、Row、Stack,再到业务组件 Progress、TextInput、List 等,框架提供了覆盖绝大多数场景的原生组件,且全部支持属性链式配置。其四,动画系统强大animateTo 显式动画与属性隐式动画相结合,配合 Curve 曲线系统与 PlayMode 播放模式,能够轻松实现呼吸动效、转场动画等复杂视觉效果。其五,Builder 函数复用@Builder 装饰器允许将 UI 片段抽离为可复用函数,支持参数传递,实现了 UI 逻辑的模块化与组合化。

本文要深入剖析的"学无涯"应用,正是基于 ArkUI 框架打造的一款在线教育产品。该应用面向终身学习者,提供课程学习、进度跟踪、在线考试、题库练习、社区问答、个人中心等六大核心功能模块,覆盖了在线教育的完整业务闭环。在视觉设计上,应用采用教育蓝(#1E40AF)作为主色调,传递专业、信赖的品牌调性;活力橙(#F97316)作为辅助色,用于强调交互元素与重点数据,形成冷暖对比的视觉张力。整体界面采用浅色设计语言,卡片式布局配合圆角与微妙阴影,营造出清爽、现代的学习氛围。

接下来,本文将从架构设计、色彩体系、数据模型、组件实现等多个维度,对该源码进行逐行逐段的深度解析,帮助读者全面掌握 ArkUI 声明式开发的实战技巧与设计思想。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、整体架构流程图

为了从宏观层面把握当前页面的系统架构,我们首先通过 Mermaid 流程图展示其整体结构与数据流向。

辅助支撑层

弹窗系统层

功能模块层

视图渲染层

数据模型层

组件主体层

入口层

@Entry 页面入口

@Component Page807

@State 状态变量群

build() 根布局

CourseItem 课程模型

LearnItem 学习模型

ExamItem 考试模型

QuestionItem 题库模型

QaItem 问答模型

StudentItem 学员模型

头部区域 headerStudy

Tab内容区 六模块切换

底部Tab栏 tabBar

课程模块 tabCourse

学习模块 tabLearn

考试模块 tabExam

题库模块 tabQuestion

社区模块 tabQa

我的模块 tabMine

遮罩层 modalOverlay

添加面板 panelAdd

编辑面板 panelEdit

删除面板 panelDel

COLORS 色彩体系

TAB_LIST 元数据

工具函数集

架构解读

从整体架构图可以清晰看出,当前页面采用了分层设计思想,各层职责明确、耦合度低,展现了良好的工程化素养。

入口层是整个页面的起点,@Entry 装饰器标记的组件将作为页面根节点被系统加载,这是 ArkUI 页面的标准入口方式。组件主体层是核心枢纽,Page807 结构体通过 @Component 装饰器成为自定义组件,内部维护一组 @State 状态变量驱动视图更新,build() 方法则负责构建完整的 UI 树结构。

数据模型层包含六个 @Observed 装饰的业务实体类,分别对应六大功能模块的数据结构。这些模型类不仅定义了数据字段,还通过 @Observed 装饰器获得了被观察能力——当其属性变化时,引用该对象的视图会自动刷新。这是 ArkUI 响应式编程的重要基石。

视图渲染层采用经典的三段式布局:顶部头部区域展示用户信息与搜索入口,中部 Tab 内容区根据当前选中 Tab 动态切换六个功能模块,底部 Tab 栏提供导航切换能力。这种布局模式在移动端应用中极为常见,符合用户的操作习惯与认知预期。

功能模块层是业务逻辑的具体承载者,六个 Tab 对应六个 Builder 函数,每个函数独立负责一个模块的 UI 构建。这种模块化拆分方式使得代码职责单一、易于维护,新增或修改某个模块时不会影响其他模块。

弹窗系统层采用遮罩加面板的经典实现方案,通过三个布尔状态变量控制三种弹窗的显隐。遮罩层统一处理点击关闭逻辑,三个面板分别对应添加、编辑、删除三种操作场景,结构清晰、复用性强。

辅助支撑层则提供了全局色彩常量、Tab 元数据配置以及工具函数集,是整个页面的基础设施。色彩体系的集中管理确保了视觉一致性,工具函数的封装避免了重复代码,元数据驱动的 Tab 配置则提升了可扩展性。

三、色彩体系设计

色彩是 UI 设计的灵魂,一套设计精良的色彩体系能够传递品牌调性、引导用户视线、强化信息层级。当前页面的色彩系统通过 TypeScript 接口与常量对象的组合方式实现,既保证了类型安全,又便于全局统一管理。

首先定义的是 ColorPalette 接口,它规范了色彩调色板的结构契约:

interface ColorPalette {
  bg: string; card: string; chip: string;
  title: string; sub: string; text3: string;
  blue: string; blueD: string;
  orange: string; orangeD: string;
  green: string; red: string;
  line: string; tabOn: string; mask: string;
}

该接口包含 14 个色值字段,按功能可划分为四组。背景色组包括 bg(页面背景)、card(卡片背景)、chip(标签/芯片背景),构建了页面的基础视觉层次。文字色组包括 title(一级标题)、sub(二级正文)、text3(辅助说明),形成了三级文字灰度体系,确保信息层级清晰可读。主题色组包括 blueblueD(教育蓝及其深色变体)、orangeorangeD(活力橙及其深色变体),是品牌识别的核心色彩。功能色组包括 green(成功/完成)、red(错误/警告),以及 line(分割线)、tabOn(Tab选中态)、mask(弹窗遮罩)等辅助色值。

具体的色值常量定义如下:

const COLORS: ColorPalette = {
  bg: '#F0F5FF', card: '#FFFFFF', chip: '#E0ECFF',
  title: '#1E293B', sub: '#64748B', text3: '#94A3B8',
  blue: '#1E40AF', blueD: '#1E3A8A',
  orange: '#F97316', orangeD: '#EA580C',
  green: '#10B981', red: '#EF4444',
  line: '#E2E8F0', tabOn: '#1E40AF',
  mask: 'rgba(0,0,0,0.5)',
};

从色值选择来看,设计者的考量非常精细。背景色 #F0F5FF 是一种淡蓝灰色,比纯白更护眼,同时与教育蓝主色调相呼应,营造出沉浸的学习氛围。卡片色使用纯白 #FFFFFF,在淡蓝背景上形成清晰的视觉分层。标签背景色 #E0ECFF 是教育蓝的极浅变体,用于标签、进度条背景等场景,保持了色彩家族的统一性。

文字色采用了经典的三级灰度方案:深灰蓝 #1E293B 用于标题,保证最强的可读性;中灰 #64748B 用于正文内容,长时间阅读不疲劳;浅灰 #94A3B8 用于辅助说明与次要信息,降低视觉噪音。这种从 Slate 色系中选取的灰度方案,比纯灰色更具品质感,与蓝色主调也更为协调。

主题色方面,教育蓝 #1E40AF 是一种偏深的宝蓝色,传达专业、稳重、可信赖的品牌感受,非常适合教育类产品。活力橙 #F97316 则是一种明亮的橙色,用于价格、按钮、强调数据等需要吸引用户注意的场景,与蓝色形成强烈的冷暖对比,有效提升了界面的视觉活力。

功能色方面,绿色 #10B981 代表成功与完成,红色 #EF4444 代表警告与危险,这是行业通用的语义色彩,用户无需学习即可理解。遮罩色使用 rgba(0,0,0,0.5) 半透明黑色,既能突出弹窗内容,又不会完全遮挡底层视图。

整体而言,这套色彩体系设计规范、语义清晰、层次分明,体现了专业的 UI 设计水准。通过集中管理的方式,所有组件都从 COLORS 常量中取色,确保了全局视觉的一致性,也为后续主题切换功能预留了扩展空间。

四、Tab元数据与辅助数据

在声明式 UI 开发中,数据驱动视图是核心理念。Tab 导航作为应用的核心骨架,其配置数据的设计直接影响着代码的可维护性与可扩展性。

TabMeta 接口与 TAB_LIST 常量

首先定义了 Tab 元数据接口 TabMeta,规范了每个 Tab 项的数据结构:

interface TabMeta { name: string; icon: string; index: number; }

该接口包含三个字段:name 表示 Tab 的显示名称,icon 表示 Tab 的图标(使用 Emoji 字符),index 表示 Tab 的索引编号。简单的三个字段涵盖了 Tab 展示所需的全部信息。

基于此接口,定义了 TAB_LIST 常量数组:

const TAB_LIST: TabMeta[] = [
  { name: '课程', icon: '📚', index: 0 },
  { name: '学习', icon: '📖', index: 1 },
  { name: '考试', icon: '📝', index: 2 },
  { name: '题库', icon: '🎯', index: 3 },
  { name: '社区', icon: '💡', index: 4 },
  { name: '我的', icon: '👤', index: 5 },
];

六个 Tab 项按照业务逻辑顺序排列,从"课程"到"我的",构成了在线教育应用的完整功能闭环。每个 Tab 都配有语义化的 Emoji 图标,既丰富了视觉表现,又降低了文字理解成本。

这种元数据驱动的设计模式具有诸多优势。其一,配置与逻辑分离:Tab 的数量、名称、图标等信息全部集中在数据配置中,底部 Tab 栏组件只需遍历 TAB_LIST 数组即可渲染,无需硬编码六个 Tab 项。其二,易于扩展:如果未来需要新增或调整 Tab,只需修改 TAB_LIST 数组,而无需改动渲染逻辑。其三,类型安全:TypeScript 接口确保了每个 Tab 项的字段完整性,避免遗漏或错误。

图表辅助数据

除了 Tab 元数据,源码中还定义了图表相关的辅助数据:

const MONTHS: string[] = ['3月', '4月', '5月', '6月', '7月', '8月'];
const STUDY_VAL: number[] = [28, 45, 62, 58, 72, 85];
const CHART_MAX: number = Math.max(...STUDY_VAL) + 10;

MONTHS 数组定义了柱状图的横轴标签,展示近 6 个月的时间跨度。STUDY_VAL 数组定义了对应月份的学习时长数据(单位为小时),呈现出整体上升的学习趋势。CHART_MAX 则通过 Math.max 函数计算出数据中的最大值并额外增加 10 作为图表的上限值,确保最高的柱子不会贴顶,留出适当的视觉呼吸空间。这种动态计算图表最大值的方式,比硬编码固定最大值更加灵活,能够适应不同的数据范围。

课程分类数据

课程模块还定义了分类数据接口与常量:

interface CategoryItem { name: string; icon: string; count: number; }
const CATEGORY_LIST: CategoryItem[] = [
  { name: '鸿蒙开发', icon: '📱', count: 128 },
  { name: '前端开发', icon: '💻', count: 256 },
  { name: '后端开发', icon: '🖥️', count: 189 },
  { name: '移动开发', icon: '📲', count: 145 },
  { name: '人工智能', icon: '🤖', count: 98 },
  { name: 'UI设计', icon: '🎨', count: 167 },
  { name: '产品经理', icon: '📊', count: 76 },
  { name: '数据科学', icon: '📈', count: 112 },
];

CategoryItem 接口包含分类名称、图标和课程数量三个字段。CATEGORY_LIST 数组包含 8 个课程分类,覆盖了当前主流的技术方向。这种数据结构设计使得分类导航区域可以通过 ForEach 循环渲染,代码简洁且易于维护。

五、工具函数

工具函数是代码库中的"瑞士军刀",它们封装了通用的业务逻辑计算,避免了重复代码,提升了代码的可读性与可维护性。当前页面共实现了三个工具函数,分别服务于不同的业务场景。

examColor —— 考试状态色彩映射

function examColor(s: string): string {
  if (s === '未开始') return COLORS.text3;
  if (s === '进行中') return COLORS.orange;
  if (s === '已完成') return COLORS.green;
  if (s === '已过期') return COLORS.red;
  return COLORS.text3;
}

examColor 函数接收一个状态字符串参数,返回对应的颜色值。函数内部通过多分支条件判断实现了四种考试状态到四种语义色彩的映射:未开始状态返回灰色(text3),表示中性待处理;进行中状态返回活力橙,提示用户当前正在进行需要关注;已完成状态返回绿色,表示成功与完成;已过期状态返回红色,起到警示作用。

这种状态-色彩映射函数的设计模式在 UI 开发中非常常见。它将业务状态与视觉表现解耦,调用方只需传入状态字符串即可获得正确的颜色,无需关心具体的色值与映射规则。如果未来需要调整状态对应的颜色,只需修改这一个函数即可,无需在多处代码中逐一查找替换。

accuracyColor —— 正确率色彩分级

function accuracyColor(a: number): string {
  if (a >= 80) return COLORS.green;
  if (a >= 60) return COLORS.orange;
  return COLORS.red;
}

accuracyColor 函数接收一个数字类型的正确率参数,根据数值区间返回不同的颜色。该函数实现了三级色彩分级:正确率 80% 及以上返回绿色,表示优秀;60% 至 79% 返回橙色,表示中等需加强;60% 以下返回红色,表示较差需要重点提升。

这个函数体现了数据可视化中的色彩编码思想——通过颜色的语义差异,让用户在一瞥之间就能快速判断数据的好坏程度。在题库模块中,正确率数字和进度条颜色都通过该函数动态计算,实现了数据与视觉的统一联动。

formatTime —— 学习时长格式化

function formatTime(hours: number): string {
  const h = Math.floor(hours);
  const m = Math.floor((hours - h) * 60);
  return `${h}h${m}m`;
}

formatTime 函数将小数形式的小时数转换为"XhYm"格式的可读字符串。函数首先通过 Math.floor 取整得到小时数,然后将小数部分乘以 60 并取整得到分钟数,最后使用模板字符串拼接为目标格式。例如,输入 28.5 将输出 “28h30m”,输入 8.5 将输出 “8h30m”。

这个函数的应用场景非常广泛,头部区域的总学习时长、学习列表中的每门课程学习时长、个人中心的总时长统计等都需要用到它。将时间格式化逻辑抽离为公共函数,确保了全页展示格式的一致性,也避免了重复的计算代码。

三个工具函数虽然代码量都不大,但它们体现了良好的编程习惯:单一职责、语义命名、复用优先。这些小函数积累起来,就能显著提升整个代码库的质量与可维护性。

六、数据模型层

数据模型是应用的骨架,定义了业务实体的结构与行为。在 ArkUI 框架中,@Observed 装饰器赋予了类对象被观察的能力——当对象的属性发生变化时,所有引用该属性的 UI 节点会自动刷新。当前页面共定义了六个数据模型类,分别对应六大功能模块的核心业务实体。

CourseItem —— 课程模型

@Observed
export class CourseItem {
  name: string; teacher: string; lessons: number; price: number; rating: number; icon: string;
  constructor(name: string, teacher: string, lessons: number, price: number, rating: number, icon: string) {
    this.name = name; this.teacher = teacher; this.lessons = lessons;
    this.price = price; this.rating = rating; this.icon = icon;
  }
}

CourseItem 是课程数据模型,包含六个属性:name 为课程名称,teacher 为授课讲师姓名,lessons 为课程总课时数,price 为课程价格,rating 为课程评分(满分 5 分),icon 为课程图标(使用 Emoji 字符)。构造函数接收六个参数并依次赋值给对应属性。

这个模型类的设计紧扣在线教育的核心业务要素。课程名称与讲师是用户选择课程时最关注的信息;课时数反映了课程的内容量与学习周期;价格是付费转化的关键因素;评分是社会证明,影响用户的购买决策;图标则提供了视觉识别点,增强了卡片的表现力。

LearnItem —— 学习进度模型

@Observed
export class LearnItem {
  name: string; progress: number; currentLesson: number; totalLessons: number; studyTime: number;
  constructor(name: string, progress: number, currentLesson: number, totalLessons: number, studyTime: number) {
    this.name = name; this.progress = progress; this.currentLesson = currentLesson;
    this.totalLessons = totalLessons; this.studyTime = studyTime;
  }
}

LearnItem 是学习进度模型,描述了用户在某门课程中的学习状态。五个属性各司其职:name 是课程名称,progress 是百分比形式的整体进度(0-100),currentLesson 是当前学到的第几课,totalLessons 是课程总课时,studyTime 是累计学习时长(小时)。

这个模型的设计体现了学习进度的多维度展示思想。百分比进度提供了宏观的完成度感知,当前课时/总课时提供了具体的位置锚点,学习时长则反映了用户投入的时间成本。三种维度的数据相互补充,让用户对自己的学习状态有全面清晰的认知。

ExamItem —— 考试模型

@Observed
export class ExamItem {
  name: string; subject: string; time: string; status: string; score: number;
  constructor(name: string, subject: string, time: string, status: string, score: number) {
    this.name = name; this.subject = subject; this.time = time;
    this.status = status; this.score = score;
  }
}

ExamItem 是考试数据模型,包含五个属性:name 为考试名称,subject 为所属科目,time 为考试时间(字符串格式),status 为考试状态,score 为考试分数。

考试状态字段是该模型的核心语义字段,支持"未开始"、“进行中”、“已完成”、"已过期"四种状态,配合前面介绍的 examColor 工具函数,可以动态渲染不同的视觉样式。分数字段仅在"已完成"状态下有意义,这在 UI 层通过条件渲染进行了处理。

QuestionItem —— 题库模型

@Observed
export class QuestionItem {
  type: string; total: number; done: number; accuracy: number; icon: string;
  constructor(type: string, total: number, done: number, accuracy: number, icon: string) {
    this.type = type; this.total = total; this.done = done;
    this.accuracy = accuracy; this.icon = icon;
  }
}

QuestionItem 是题库练习模型,描述了某一类题型的练习数据。五个属性分别是:type 为题型名称,total 为该题型总题数,done 为已做题数,accuracy 为正确率,icon 为题型图标。

该模型的设计突出了刷题类产品的核心数据指标:总量展示题库丰富度,已做量反映练习进度,正确率衡量学习效果。三者结合,用户可以清晰了解自己在每种题型上的掌握程度,从而有针对性地进行强化练习。

QaItem —— 问答模型

@Observed
export class QaItem {
  question: string; asker: string; answers: number; likes: number; tag: string; bestAnswer: string;
  constructor(q: string, a: string, an: number, l: number, t: string, b: string) {
    this.question = q; this.asker = a; this.answers = an;
    this.likes = l; this.tag = t; this.bestAnswer = b;
  }
}

QaItem 是社区问答模型,承载了社区模块的核心数据。六个属性包括:question 为问题标题,asker 为提问者昵称,answers 为回答数量,likes 为点赞数,tag 为问题标签分类,bestAnswer 为最佳回答的预览内容。

这个模型的设计亮点在于 bestAnswer 字段——在问答列表中直接展示最佳回答的预览内容,让用户无需进入详情页就能快速获取有价值的信息,提升了信息浏览效率。这也是社区类产品常见的设计手法,通过"摘要+详情"的双层信息结构,兼顾浏览效率与内容深度。

StudentItem —— 学员模型

@Observed
export class StudentItem {
  name: string; studyDays: number; totalHours: number; coursesDone: number; certs: number; avatar: string;
  constructor(name: string, sd: number, th: number, cd: number, ce: number, av: string) {
    this.name = name; this.studyDays = sd; this.totalHours = th;
    this.coursesDone = cd; this.certs = ce; this.avatar = av;
  }
}

StudentItem 是学员信息模型,描述了学习者的个人数据与学习成就。六个属性分别是:name 为学员昵称,studyDays 为累计学习天数,totalHours 为总学习时长,coursesDone 为已完成课程数,certs 为获得证书数,avatar 为头像。

该模型聚合了学员的核心学习数据,既用于头部区域的个性化问候,也用于个人中心的数据展示,还可用于学习排行榜的渲染。一个模型类在多个场景中复用,体现了数据模型设计的通用性原则。

模型设计总结

六个数据模型类虽然结构简单,但设计精良。每个类都使用 @Observed 装饰器标注,确保数据变更能驱动视图更新;构造函数参数完整,支持便捷的实例化;字段命名语义清晰,见名知意。这种轻量级的数据模型设计非常适合声明式 UI 开发——模型只负责承载数据,视图通过 ForEach 遍历渲染,逻辑清晰、易于理解。

七、组件主体结构

组件主体是整个页面的核心骨架,它将状态管理、布局组织、模块切换整合在一起,是理解整个页面运作机制的关键入口。

@Entry
@Component
struct Page807 {
  @State currentTab: number = 0;
  @State showAdd: boolean = false;
  @State showEdit: boolean = false;
  @State showDel: boolean = false;
  @State selectedId: number = -1;
  @State breathScale: number = 1.0;
  private student: StudentItem = STUDENT_LIST[0];

页面组件使用 @Entry@Component 双重装饰器修饰。@Component 表示这是一个自定义组件,可以拥有自己的状态与构建方法;@Entry 则表示这是页面的入口组件,系统加载页面时会从该组件开始渲染。struct 关键字定义了结构体类型,这是 ArkTS 中自定义组件的标准声明方式。

状态变量是组件的"记忆中枢",它们的变化会触发视图的重新渲染。六个状态变量各司其职:currentTab 记录当前选中的 Tab 索引,初始值为 0 表示默认显示课程 Tab;showAddshowEditshowDel 三个布尔变量分别控制添加、编辑、删除三种弹窗的显示与隐藏;selectedId 记录当前选中的列表项索引,用于在编辑或查看详情时传递上下文;breathScale 存储呼吸动画的缩放比例,用于课程卡片图标的呼吸动效。

除了状态变量,组件还定义了一个私有成员 student,直接从 Mock 数据列表中取第一个元素作为当前登录学员。在实际项目中,这里会替换为从接口获取的登录用户信息。

  aboutToAppear(): void {
    animateTo({ duration: 2000, curve: Curve.EaseInOut, iterations: -1, playMode: PlayMode.Alternate }, () => {
      this.breathScale = 1.05;
    });
  }

aboutToAppear 是 ArkUI 组件的生命周期函数,在组件即将出现时被调用。这里利用该生命周期启动了一个无限循环的呼吸动画animateTo 函数接收两个参数:第一个是动画配置对象,第二个是动画结束状态的闭包函数。

动画配置包含四个关键参数:duration 设置动画时长为 2000 毫秒(2 秒),确保呼吸节奏舒缓自然;curve 设置缓动曲线为 EaseInOut,即先加速后减速,模拟真实呼吸的节奏感;iterations 设置为 -1 表示无限循环播放;playMode 设置为 Alternate 表示交替播放,即从初始状态到目标状态后,再反向从目标状态回到初始状态,形成完整的呼吸周期。

动画的目标状态是将 breathScale 从初始的 1.0 变为 1.05,即放大 5%。配合交替播放模式,实际效果是图标在 1.0 到 1.05 之间来回缩放,产生轻柔的呼吸感。这种微动效能够吸引用户注意力,为界面增添活力与灵动感。

  build() {
    Stack({ alignContent: Alignment.Center }) {
      Column() {
        this.headerStudy()
        Scroll() {
          Column() {
            if (this.currentTab === 0) this.tabCourse()
            else if (this.currentTab === 1) this.tabLearn()
            else if (this.currentTab === 2) this.tabExam()
            else if (this.currentTab === 3) this.tabQuestion()
            else if (this.currentTab === 4) this.tabQa()
            else if (this.currentTab === 5) this.tabMine()
          }
          .width('100%')
          .padding({ left: 16, right: 16, top: 12, bottom: 100 })
        }
        .scrollBar(BarState.Off)
        .width('100%')
        .layoutWeight(1)
        this.tabBar()
      }
      .width('100%')
      .height('100%')
      .backgroundColor(COLORS.bg)

build() 方法是组件的核心,负责描述 UI 结构。根布局使用 Stack 堆叠容器,对齐方式设为居中。Stack 内部包含两层:第一层是主内容区(Column),第二层是弹窗遮罩与面板(通过条件渲染控制)。Stack 布局的特点是子组件沿 Z 轴堆叠,非常适合实现弹窗浮层效果。

主内容区使用 Column 垂直布局,从上到下依次排列头部区域、可滚动内容区和底部 Tab 栏。内容区包裹在 Scroll 组件中,支持垂直滚动浏览。滚动条设置为 BarState.Off 即隐藏状态,保持界面简洁。内容区的内边距设置为左右 16、顶部 12、底部 100,其中底部的 100px 留白是为了防止内容被底部 Tab 栏遮挡。

Tab 内容切换通过 if-else if 条件分支实现,根据 currentTab 的值选择渲染对应的 Builder 函数。这种方式虽然在 Tab 数量较多时代码略显冗长,但逻辑直观、性能优良——每次只渲染一个 Tab 的内容,减少了不必要的组件创建与销毁。

      if (this.showAdd || this.showEdit || this.showDel) this.modalOverlay()
      if (this.showAdd) this.panelAdd()
      if (this.showEdit) this.panelEdit()
      if (this.showDel) this.panelDel()
    }
    .width('100%')
    .height('100%')
  }

Stack 的第二层是弹窗系统,包含遮罩层和三个功能面板。遮罩层在任意一种弹窗显示时都会出现,提供半透明黑色背景并处理点击关闭事件。三个功能面板则分别由各自的状态变量控制显隐。这种设计实现了弹窗与主界面的完全解耦,弹窗逻辑独立、互不干扰。

整体来看,组件主体的结构设计清晰合理:Stack 承载主内容与弹窗的层叠关系,Column 组织纵向布局,Scroll 提供滚动能力,Builder 函数实现模块化拆分。这种架构模式在 ArkUI 开发中具有很强的通用性,值得学习与借鉴。

八、头部区域详解

头部区域是用户进入页面后首先看到的部分,承担着用户识别、信息概览、搜索入口等多重功能。headerStudy Builder 函数构建了这一关键区域。

  @Builder
  headerStudy() {
    Column() {
      Row() {
        Column() {
          Text('你好,' + this.student.name)
            .fontSize(20).fontWeight(FontWeight.Bold).fontColor(COLORS.card)
          Text('今天也要加油学习哦 💪')
            .fontSize(13).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start).layoutWeight(1)
        Text(this.student.avatar)
          .fontSize(36).width(48).height(48).textAlign(TextAlign.Center)
          .backgroundColor('rgba(255,255,255,0.2)').borderRadius(24)
      }
      .width('100%')

头部区域的第一行是用户问候区,采用水平布局。左侧为用户信息,包含两行文本:第一行是大号加粗的个性化问候语,将学员姓名动态拼接到"你好"之后,让用户感受到专属感;第二行是鼓励语,搭配 Emoji 表情增添亲和力,字体使用半透明白色降低视觉权重。

右侧是用户头像,使用 Emoji 字符作为头像内容,放在一个 48x48 的圆形背景中。背景色使用 rgba(255,255,255,0.2) 即 20% 透明度的白色,在蓝色背景上形成柔和的视觉层次。这种半透明白色的设计手法在深色背景上非常实用,既能形成可辨识的色块边界,又不会破坏整体的色彩和谐。

      Row() {
        Column() {
          Text(this.student.studyDays.toString())
            .fontSize(24).fontWeight(FontWeight.Bold).fontColor(COLORS.card)
          Text('学习天数').fontSize(12).fontColor('rgba(255,255,255,0.8)').margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
        Column() {
          Text(formatTime(this.student.totalHours))
            .fontSize(24).fontWeight(FontWeight.Bold).fontColor(COLORS.card)
          Text('总学习时长').fontSize(12).fontColor('rgba(255,255,255,0.8)').margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
        Column() {
          Text('28天').fontSize(24).fontWeight(FontWeight.Bold).fontColor(COLORS.card)
          Text('连续打卡').fontSize(12).fontColor('rgba(255,255,255,0.8)').margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
      }
      .width('100%').margin({ top: 20 })

第二行是学习数据概览,采用三等分的水平布局展示三项核心数据:学习天数、总学习时长、连续打卡天数。每项数据都采用"大数字+小标签"的经典排版方式,数字使用 24px 加粗白色,标签使用 12px 半透明白色,形成清晰的视觉层级。

总学习时长调用了 formatTime 工具函数进行格式化,将小数小时数转换为易读的"XhYm"格式。连续打卡天数直接以"28天"的形式展示,数字与单位合并显示,简洁直观。

三个数据项通过 layoutWeight(1) 平均分配宽度,alignItems(HorizontalAlign.Center) 使内容水平居中,整体呈现出均衡对称的视觉美感。这种数据概览设计在教育类 App 中非常常见,它既能让用户快速了解自己的学习成果,又能通过数字激励用户持续学习。

      Row() {
        Text('🔍').fontSize(16).margin({ right: 8 })
        TextInput({ placeholder: '搜索课程、考试、题库...' })
          .placeholderColor(COLORS.text3).fontSize(14).fontColor(COLORS.title)
          .backgroundColor(Color.Transparent).layoutWeight(1)
      }
      .width('100%').height(40).padding({ left: 14, right: 14 })
      .backgroundColor(COLORS.card).borderRadius(20).margin({ top: 18 })
    }
    .width('100%').padding({ left: 20, right: 20, top: 48, bottom: 24 })
    .backgroundColor(COLORS.blue).borderRadius({ bottomLeft: 24, bottomRight: 24 })
  }

第三行是搜索栏,采用白色圆角胶囊造型,在蓝色背景上非常醒目。搜索栏内部左侧是放大镜图标,右侧是 TextInput 文本输入框,提示文字为"搜索课程、考试、题库…",暗示搜索功能覆盖多个模块。

输入框的背景设置为透明(Color.Transparent),使其融入白色搜索栏背景,视觉上更加整体。提示文字颜色使用 COLORS.text3 即浅灰色,与白色背景形成适当的对比度,既可见又不会喧宾夺主。

整个头部区域的容器使用 Column 垂直布局,背景色为教育蓝(COLORS.blue),底部左右两个圆角设为 24px,形成"头部下沉"的视觉效果。内边距设置为左右 20、顶部 48(留出状态栏空间)、底部 24,整体空间感舒适。顶部的大圆角设计是当前移动端 UI 的流行趋势,能够柔化界面的硬朗感,营造更加亲和的视觉氛围。

九、Tab0 课程模块:双列卡片瀑布流

课程模块是在线教育应用的核心入口,承担着课程浏览、分类筛选、课程推荐等重要功能。tabCourse Builder 函数构建了一个内容丰富、层次分明的课程首页。

分类导航区

  @Builder
  tabCourse() {
    Column() {
      Row() {
        Text('课程分类').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text('更多 >').fontSize(12).fontColor(COLORS.blue).margin({ left: 'auto' })
      }.width('100%').margin({ bottom: 10 })

模块开头是分类导航的标题行,左侧为加粗的"课程分类"标题,右侧为蓝色的"更多 >"链接,暗示点击可查看全部分类。margin({ left: 'auto' }) 是 ArkUI 中实现"左右两端对齐"的常用技巧——在 Row 容器中,给右侧元素设置左外边距为自动,会将其推送到最右端。

      Row() {
        ForEach(CATEGORY_LIST.slice(0, 4), (cat: CategoryItem) => {
          this.categoryItem(cat)
        }, (cat: CategoryItem) => cat.name)
      }.width('100%').justifyContent(FlexAlign.SpaceBetween)

      Row() {
        ForEach(CATEGORY_LIST.slice(4, 8), (cat: CategoryItem) => {
          this.categoryItem(cat)
        }, (cat: CategoryItem) => cat.name)
      }.width('100%').justifyContent(FlexAlign.SpaceBetween).margin({ top: 10 })

分类图标采用两行四列的宫格布局,共展示 8 个课程分类。代码通过 CATEGORY_LIST.slice(0, 4)CATEGORY_LIST.slice(4, 8) 将 8 个分类拆分为两组,每组用一个 Row 容器水平排列,justifyContent(FlexAlign.SpaceBetween) 使四个分类项均匀分布。

这种手动拆分数组的方式虽然不如 Grid 组件优雅,但在简单的固定布局场景下代码更直观、性能更好。每个分类项通过 categoryItem Builder 函数渲染,实现了 UI 逻辑的复用。

  @Builder
  categoryItem(cat: CategoryItem) {
    Column() {
      Text(cat.icon).fontSize(26)
      Text(cat.name).fontSize(12).fontColor(COLORS.title).margin({ top: 4 })
      Text(cat.count + '门').fontSize(10).fontColor(COLORS.text3).margin({ top: 2 })
    }
    .width(70).alignItems(HorizontalAlign.Center)
    .padding({ top: 8, bottom: 8 })
    .backgroundColor(COLORS.card).borderRadius(10)
    .onClick(() => { this.showAdd = true; })
  }

categoryItem 函数构建单个分类卡片。卡片内容垂直排列:顶部是 26px 的分类图标 Emoji,中间是 12px 的分类名称,底部是 10px 的课程数量。三层信息字号递减、颜色由深到浅,形成清晰的信息层级。

卡片宽度固定为 70px,背景为白色,圆角 10px,内容居中对齐。点击分类会触发添加弹窗(此处为演示效果,实际应跳转到分类课程列表页)。

热门课程双列卡片

      Row() {
        Text('热门课程').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text('全部 >').fontSize(13).fontColor(COLORS.blue).margin({ left: 'auto' })
      }
      .width('100%').margin({ top: 16, bottom: 12 })

热门课程区块的标题行使用更大的字号(18px),视觉权重高于分类标题,突出了"热门课程"作为核心内容的地位。

      Row() {
        Column({ space: 12 }) {
          ForEach(COURSE_LEFT, (item: CourseItem) => { this.courseCard(item) }, (item: CourseItem) => item.name)
        }.layoutWeight(1)
        Column({ space: 12 }) {
          ForEach(COURSE_RIGHT, (item: CourseItem) => { this.courseCard(item) }, (item: CourseItem) => item.name)
        }.layoutWeight(1).margin({ left: 10 })
      }
      .width('100%').alignItems(VerticalAlign.Top)

双列卡片布局是课程模块的视觉焦点。实现方式是使用一个 Row 容器包含两个 Column 子容器,每个 Column 占一半宽度(layoutWeight(1)),分别渲染左列和右列的课程卡片。两列之间通过左边距留出 10px 的间距。

课程数据被拆分为 COURSE_LEFTCOURSE_RIGHT 两个数组,分别对应左右两列。这种拆分方式虽然牺牲了一些灵活性(新增课程需要手动分配列),但换来了布局的稳定性——每列卡片高度独立,不会出现交错排列的问题。

  @Builder
  courseCard(item: CourseItem) {
    Column() {
      Stack({ alignContent: Alignment.Center }) {
        Text(item.icon).fontSize(40)
          .scale({ x: this.breathScale * 0.9 + 0.1, y: this.breathScale * 0.9 + 0.1 })
      }
      .width('100%').height(90).backgroundColor(COLORS.chip)
      .borderRadius({ topLeft: 12, topRight: 12 })

courseCard 函数构建单张课程卡片。卡片顶部是图标区域,使用 Stack 容器居中放置课程图标 Emoji。图标应用了呼吸动画效果——scale 属性绑定到 breathScale 状态变量,通过 this.breathScale * 0.9 + 0.1 的计算,将 1.0~1.05 的缩放范围映射到 1.0~1.045,即呼吸幅度约为 4.5%。

图标区域高度 90px,背景为浅蓝色(COLORS.chip),顶部两个圆角设为 12px,底部为直角,与下方的卡片内容区域自然衔接。

      Column() {
        Text(item.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
          .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
          .width('100%').height(38)
        Text('👨‍🏫 ' + item.teacher).fontSize(11).fontColor(COLORS.sub)
          .width('100%').margin({ top: 2 })
          .textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1)
        Row() {
          Text(item.lessons + '课时').fontSize(11).fontColor(COLORS.text3)
          Text('⭐' + item.rating).fontSize(11).fontColor(COLORS.orange).margin({ left: 'auto' })
        }.width('100%').margin({ top: 4 })
        Row() {
          Text('¥' + item.price).fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.orange)
          Button('加入').fontSize(11).height(26).backgroundColor(COLORS.blue)
            .borderRadius(13).padding({ left: 12, right: 12 }).margin({ left: 'auto' })
            .onClick(() => { this.showAdd = true; })
        }.width('100%').margin({ top: 6 })
      }
      .width('100%').padding(12)
    }
    .width('100%').backgroundColor(COLORS.card).borderRadius(12)
    .shadow({ radius: 4, color: 'rgba(30,64,175,0.08)', offsetY: 2 })
  }

卡片下方是课程信息区,包含四行内容。第一行是课程名称,设置了最大两行显示,超出部分用省略号截断,并固定高度为 38px,确保所有卡片的名称区域高度一致,双列布局整齐对齐。

第二行是讲师信息,前缀教师 Emoji,使用灰色小字显示。第三行左侧显示课时数,右侧显示课程评分(橙色星级),评分使用活力橙色增强视觉吸引力。第四行是价格与操作按钮,价格使用醒目的橙色加粗字体,"加入"按钮为蓝色圆角矩形,点击触发添加弹窗。

卡片整体添加了微妙的阴影效果:模糊半径 4px,颜色为带 8% 透明度的教育蓝,Y 轴偏移 2px。这种淡淡的蓝色阴影与白色卡片和蓝色主题相呼应,比普通灰色阴影更具品牌辨识度。

为你推荐区

      Row() {
        Text('🎯 为你推荐').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
      }.width('100%').margin({ top: 16, bottom: 10 })

      Column({ space: 10 }) {
        this.recommendCard(COURSE_LIST[0], '根据你的学习记录推荐')
        this.recommendCard(COURSE_LIST[2], '大家都在学的热门课程')
      }.width('100%')
    }
    .width('100%')
  }

课程模块底部是"为你推荐"区域,展示个性化推荐课程。推荐卡片使用横向列表布局,与热门课程的双列卡片形成视觉对比,丰富了页面的布局节奏。

  @Builder
  recommendCard(item: CourseItem, reason: string) {
    Row() {
      Stack({ alignContent: Alignment.Center }) { Text(item.icon).fontSize(32) }
        .width(64).height(64).backgroundColor(COLORS.chip).borderRadius(10).margin({ right: 14 })
      Column() {
        Text(item.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
          .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%')
        Text(reason).fontSize(11).fontColor(COLORS.text3).margin({ top: 4 })
        Row() {
          Text('👨‍🏫 ' + item.teacher).fontSize(11).fontColor(COLORS.sub)
          Text('⭐' + item.rating).fontSize(11).fontColor(COLORS.orange).margin({ left: 12 })
          Text('¥' + item.price).fontSize(14).fontWeight(FontWeight.Bold)
            .fontColor(COLORS.orange).margin({ left: 'auto' })
        }.width('100%').margin({ top: 6 })
      }.layoutWeight(1).alignItems(HorizontalAlign.Start)
    }
    .width('100%').padding(12).backgroundColor(COLORS.card).borderRadius(12)
    .onClick(() => { this.showEdit = true; })
  }

推荐卡片采用横向布局:左侧是 64x64 的课程图标方块,右侧是课程详细信息。信息区域包含课程名称、推荐理由、讲师/评分/价格三行内容。推荐理由是推荐卡片的特色字段,用来说明推荐原因,增强推荐的可信度与说服力,比如"根据你的学习记录推荐"、"大家都在学的热门课程"等。

整体而言,课程模块通过分类导航、热门课程、为你推荐三个层次的内容组织,构建了从"发现"到"浏览"再到"精准推荐"的完整用户路径,符合在线教育产品的用户行为规律。

十、Tab1 学习模块:进度追踪与双形态进度条

学习模块是用户日常使用最频繁的功能区,它展示了用户正在学习的所有课程及其进度状态。tabLearn Builder 函数以列表形式呈现学习进度,核心亮点是环形进度条与线性进度条的双形态展示

  @Builder
  tabLearn() {
    Column() {
      Row() {
        Text('正在学习').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text(LEARN_LIST.length + '门课程').fontSize(13).fontColor(COLORS.sub).margin({ left: 8 })
      }.width('100%').margin({ bottom: 12 })

      Column({ space: 10 }) {
        ForEach(LEARN_LIST, (item: LearnItem) => { this.learnCard(item) }, (item: LearnItem) => item.name)
      }.width('100%')
    }.width('100%')
  }

学习模块的结构简洁明了:顶部是标题行,主标题"正在学习"使用 18px 加粗字体,右侧附带课程数量的辅助说明。下方是学习卡片列表,通过 ForEach 遍历 LEARN_LIST 数组,每张卡片调用 learnCard 函数渲染。

  @Builder
  learnCard(item: LearnItem) {
    Row() {
      Stack({ alignContent: Alignment.Center }) {
        Progress({ value: item.progress, total: 100, type: ProgressType.Ring })
          .width(52).height(52).color(COLORS.blue).style({ strokeWidth: 4 })
        Text(item.progress + '%').fontSize(12).fontWeight(FontWeight.Bold).fontColor(COLORS.blue)
      }.width(52).height(52).margin({ right: 14 })

学习卡片采用横向布局,最左侧是环形进度指示器,这是整张卡片的视觉焦点。环形进度通过 ArkUI 内置的 Progress 组件实现,类型设为 ProgressType.Ring 即环形。value 属性绑定到课程的进度百分比,total 设为 100,color 设为教育蓝,strokeWidth 样式属性设置环的线条粗细为 4px。

环形进度的中心放置了百分比数字文本,使用 12px 加粗蓝色字体,与环形颜色一致。数字与环形结合,用户既能通过图形直观感受完成度,又能通过数字获取精确信息,二者互为补充。Stack 容器的居中对齐确保了数字完美位于环形中心。

      Column() {
        Text(item.name).fontSize(15).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
          .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%')
        Text('第 ' + item.currentLesson + '/' + item.totalLessons + ' 课')
          .fontSize(12).fontColor(COLORS.sub).margin({ top: 4 })
        Row() {
          Text('⏱ ' + formatTime(item.studyTime)).fontSize(11).fontColor(COLORS.text3)
          Text(item.progress === 100 ? '已完成' : '继续学习')
            .fontSize(12).fontWeight(FontWeight.Medium)
            .fontColor(item.progress === 100 ? COLORS.green : COLORS.blue)
            .margin({ left: 'auto' })
        }.width('100%').margin({ top: 6 })
        Progress({ value: item.progress, total: 100, type: ProgressType.Linear })
          .width('100%').height(5).color(COLORS.blue)
          .backgroundColor(COLORS.chip).borderRadius(2).margin({ top: 6 })
      }.layoutWeight(1).alignItems(HorizontalAlign.Start)

右侧信息区域垂直排列了四行内容。第一行是课程名称,15px 中等字重,最多显示一行,超出省略。第二行显示当前学习进度的具体位置——“第 X/Y 课”,让用户明确知道自己学到了第几课。

第三行左侧显示累计学习时长,调用 formatTime 函数格式化展示;右侧是状态文字,根据进度是否为 100% 动态切换:已完成状态显示绿色的"已完成",进行中状态显示蓝色的"继续学习"。这种条件渲染方式用简洁的三元表达式实现,逻辑清晰。

第四行是线性进度条,类型为 ProgressType.Linear,高度仅 5px,作为辅助进度指示器。进度条底色为浅蓝色(COLORS.chip),进度色为教育蓝,两端圆角 2px,视觉效果精致细腻。

一张卡片同时包含环形进度和线性进度两种进度条形态,环形进度提供"一目了然"的宏观感知,线性进度提供"精确丈量"的细节补充。这种双进度条设计是学习模块最具辨识度的视觉特征,也体现了设计者对信息展示的深度思考。

    }
    .width('100%').padding(14).backgroundColor(COLORS.card).borderRadius(12)
    .onClick(() => { this.selectedId = LEARN_LIST.indexOf(item); this.showEdit = true; })
  }

整张卡片使用白色背景和 12px 圆角,内边距 14px,点击卡片会记录当前选中项的索引并打开编辑弹窗。在实际项目中,这里应该跳转到课程学习详情页。

十一、Tab2 考试模块:答题卡与状态色彩体系

考试模块负责管理用户的考试安排与成绩查询,核心功能是展示考试列表及其状态信息。tabExam Builder 函数通过统计卡片 + 考试列表 + 考试须知的三段式结构,构建了完整的考试管理界面。

考试统计卡片

  @Builder
  tabExam() {
    Column() {
      Row() {
        Column() {
          Text('6').fontSize(22).fontWeight(FontWeight.Bold).fontColor(COLORS.blue)
          Text('总考试数').fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
        Column() {
          Text('2').fontSize(22).fontWeight(FontWeight.Bold).fontColor(COLORS.green)
          Text('已完成').fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
        Column() {
          Text('1').fontSize(22).fontWeight(FontWeight.Bold).fontColor(COLORS.orange)
          Text('进行中').fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
        Column() {
          Text('90').fontSize(22).fontWeight(FontWeight.Bold).fontColor(COLORS.red)
          Text('平均分').fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
      }
      .width('100%').padding(14).backgroundColor(COLORS.card).borderRadius(12).margin({ bottom: 12 })

考试模块顶部是一张四宫格统计卡片,横向排列四个数据指标:总考试数、已完成、进行中、平均分。每项指标采用"大数字+小标签"的经典排版,数字字号 22px 加粗,标签 11px 灰色。

有趣的是四个指标的数字颜色各不相同,分别对应不同的语义色彩:总考试数用蓝色(主题色)、已完成用绿色(成功色)、进行中用橙色(提醒色)、平均分用红色(强调色)。四种颜色的使用让数据一目了然,用户扫一眼就能快速区分各项指标的含义。这种色彩编码的数据可视化手法,在信息密度较高的统计面板中尤为有效。

考试列表

      Row() {
        Text('考试列表').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text('全部 >').fontSize(13).fontColor(COLORS.blue).margin({ left: 'auto' })
      }.width('100%').margin({ bottom: 12 })

      Column({ space: 10 }) {
        ForEach(EXAM_LIST, (item: ExamItem) => { this.examCard(item) }, (item: ExamItem) => item.name)
      }.width('100%')

考试列表部分结构简洁,标题行 + 卡片列表的标准模式。每张考试卡片通过 examCard 函数构建。

  @Builder
  examCard(item: ExamItem) {
    Row() {
      Stack({ alignContent: Alignment.Center }) { Text('📝').fontSize(26) }
        .width(48).height(48).backgroundColor(COLORS.chip).borderRadius(10).margin({ right: 14 })

考试卡片左侧是一个 48x48 的图标方块,使用纸笔 Emoji 作为考试的视觉象征,背景为浅蓝色。这一图标区块在视觉上起到了"锚点"作用,让用户在列表中快速识别考试项。

      Column() {
        Text(item.name).fontSize(15).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
          .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%')
        Row() {
          Text(item.subject).fontSize(12).fontColor(COLORS.sub)
            .backgroundColor(COLORS.chip).padding({ left: 8, right: 8, top: 2, bottom: 2 }).borderRadius(4)
          Text('📅 ' + item.time).fontSize(11).fontColor(COLORS.text3).margin({ left: 8 })
        }.width('100%').margin({ top: 6 })
        Row() {
          Text(item.status).fontSize(12).fontWeight(FontWeight.Medium).fontColor(examColor(item.status))
          if (item.status === '已完成') {
            Text(' ' + item.score + '分').fontSize(12).fontWeight(FontWeight.Bold).fontColor(COLORS.green)
          }
        }.width('100%').margin({ top: 4 })
      }.layoutWeight(1).alignItems(HorizontalAlign.Start)

中间信息区包含三行内容。第一行是考试名称,加粗显示。第二行左侧是所属科目标签,使用"标签"样式——文字放在浅蓝色背景的圆角矩形中,这是一种常见的标签(Chip)设计模式;右侧是考试时间,日历 Emoji 加上时间字符串,直观明了。

第三行是考试状态展示,这里是考试卡片最核心的语义信息。状态文字的颜色通过 examColor 工具函数动态计算:未开始为灰色、进行中为橙色、已完成为绿色、已过期为红色。颜色与状态语义一一对应,符合用户的心理模型。

当考试状态为"已完成"时,还会额外显示考试分数,分数使用绿色加粗字体,突出成绩信息。这种条件渲染的方式确保了信息展示的精准性——只有在合适的状态下才显示相关数据,避免信息冗余。

      Button(item.status === '未开始' ? '开始' : item.status === '进行中' ? '继续' : item.status === '已完成' ? '查看' : '重考')
        .fontSize(12).height(32).backgroundColor(item.status === '已过期' ? COLORS.text3 : COLORS.blue)
        .borderRadius(16).padding({ left: 14, right: 14 })
        .onClick(() => {
          if (item.status === '已完成') { this.selectedId = EXAM_LIST.indexOf(item); this.showEdit = true; }
          else { this.showAdd = true; }
        })
    }
    .width('100%').padding(14).backgroundColor(COLORS.card).borderRadius(12)
  }

卡片右侧是操作按钮,按钮文案根据考试状态动态变化:未开始显示"开始"、进行中显示"继续"、已完成显示"查看"、已过期显示"重考"。按钮颜色也做了差异化处理:已过期状态使用灰色按钮(表示禁用/次要操作),其他状态使用蓝色主按钮。

按钮的点击事件同样根据状态区分:已完成状态打开编辑弹窗(查看详情),其他状态打开添加弹窗(开始/继续考试)。这种按钮文案、颜色、点击行为全部由状态驱动的设计,是声明式 UI 的典型优势——数据决定视图,逻辑清晰可维护。

考试须知

      Column() {
        Text('📋 考试须知').fontSize(14).fontWeight(FontWeight.Medium).fontColor(COLORS.title).width('100%')
        Text('• 请在考试前确保网络稳定').fontSize(12).fontColor(COLORS.sub).width('100%').margin({ top: 6 })
        Text('• 考试过程中请勿切换页面').fontSize(12).fontColor(COLORS.sub).width('100%').margin({ top: 4 })
        Text('• 考试时间结束后自动提交').fontSize(12).fontColor(COLORS.sub).width('100%').margin({ top: 4 })
        Text('• 成绩可在考试记录中查看').fontSize(12).fontColor(COLORS.sub).width('100%').margin({ top: 4 })
      }.width('100%').padding(14).backgroundColor(COLORS.card).borderRadius(12).margin({ top: 12 })

考试模块底部是考试须知板块,以清单形式列出四条注意事项。这一设计体现了产品的人文关怀——在用户进入考试前,提前告知重要规则,避免因操作失误影响考试成绩。须知文字使用项目符号列表格式,每条间距适中,阅读体验良好。

十二、Tab3 题库模块:刷题进度与正确率色彩分级

题库模块是在线教育产品中"练习"环节的核心功能,帮助用户通过刷题巩固知识、提升应试能力。tabQuestion Builder 函数构建了一个按题型分类的刷题列表,核心特色是正确率的色彩分级可视化

  @Builder
  tabQuestion() {
    Column() {
      Row() {
        Text('题库练习').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text('开始刷题 >').fontSize(13).fontColor(COLORS.orange).margin({ left: 'auto' })
      }.width('100%').margin({ bottom: 12 })

      Column({ space: 10 }) {
        ForEach(QUESTION_LIST, (item: QuestionItem) => { this.questionCard(item) }, (item: QuestionItem) => item.type)
      }.width('100%')
    }.width('100%')
  }

题库模块的标题行有一个细节值得注意:右侧的行动入口"开始刷题 >“使用了活力橙色,而非其他模块常用的蓝色。这是因为题库模块的核心行动是"开始练习”,使用橙色作为强调色能够更好地吸引用户点击,提升转化率。色彩心理学中,橙色代表活力、行动、刺激感,非常适合用于号召性按钮。

  @Builder
  questionCard(item: QuestionItem) {
    Row() {
      Stack({ alignContent: Alignment.Center }) { Text(item.icon).fontSize(24) }
        .width(46).height(46).backgroundColor(COLORS.chip).borderRadius(10).margin({ right: 14 })

每张题库卡片左侧是题型图标方块,尺寸为 46x46,比考试卡片的图标略小,体现了细微的层级差异。不同的题型配有不同的 Emoji 图标,如选择题用✅、判断题用⚡、填空题用📝等,增强了题型的视觉辨识度。

      Column() {
        Row() {
          Text(item.type).fontSize(15).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
          Text(' 共' + item.total + '题').fontSize(12).fontColor(COLORS.text3)
        }.width('100%')
        Row() {
          Text('已做' + item.done + '题').fontSize(12).fontColor(COLORS.sub)
          Text(' 正确率 ').fontSize(12).fontColor(COLORS.sub).margin({ left: 16 })
          Text(item.accuracy + '%').fontSize(12).fontWeight(FontWeight.Bold).fontColor(accuracyColor(item.accuracy))
        }.width('100%').margin({ top: 4 })
        Progress({ value: item.done, total: item.total, type: ProgressType.Linear })
          .width('100%').height(4).color(accuracyColor(item.accuracy))
          .backgroundColor(COLORS.line).borderRadius(2).margin({ top: 6 })
      }.layoutWeight(1).alignItems(HorizontalAlign.Start)

中间信息区分为三行。第一行是题型名称和总题数,题型名称用较深的主色,总题数用较浅的辅助色,主次分明。

第二行是核心数据行:左侧显示已做题数,右侧显示正确率。正确率的百分比数字使用 accuracyColor 函数动态着色——80% 以上绿色、60-79% 橙色、60% 以下红色。用户一眼就能看出自己在各题型上的掌握程度。

第三行是刷题进度条,进度条的颜色同样由正确率决定。这意味着进度条颜色不仅表示进度,还隐含了质量信息:绿色进度条表示"又快又好",橙色表示"进度尚可但需加强",红色则警示"进度慢且正确率低"。这种将进度与质量双重信息编码到同一视觉元素中的设计手法,非常巧妙且高效。

      Button('练习').fontSize(12).height(32).backgroundColor(COLORS.orange)
        .borderRadius(16).padding({ left: 14, right: 14 })
        .onClick(() => { this.showAdd = true; })
    }
    .width('100%').padding(14).backgroundColor(COLORS.card).borderRadius(12)
  }

卡片右侧的"练习"按钮同样使用活力橙色,与标题行的"开始刷题"相呼应,强化了题库模块的橙色主题识别。统一的行动色设计有助于用户形成条件反射——看到橙色按钮就知道是"开始行动"的意思。

十三、Tab4 社区模块:问答卡片与最佳回答预览

社区模块是学习者交流互动的场所,用户可以在这里提问、回答、分享学习心得。tabQa Builder 函数以问答列表的形式展示热门问题,核心亮点是最佳回答预览功能——用户在列表页就能直接看到每个问题的最佳回答摘要,大幅提升了信息获取效率。

  @Builder
  tabQa() {
    Column() {
      Row() {
        Text('热门问答').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text('提问 +').fontSize(13).fontColor(COLORS.blue).margin({ left: 'auto' })
          .onClick(() => { this.showAdd = true; })
      }.width('100%').margin({ bottom: 12 })

      Column({ space: 10 }) {
        ForEach(QA_LIST, (item: QaItem) => { this.qaCard(item) }, (item: QaItem) => item.question)
      }.width('100%')
    }.width('100%')
  }

社区模块标题行右侧是"提问 +"入口,使用蓝色链接样式,点击可触发添加弹窗(发布新问题)。加号符号直观地表达了"新增"的语义,降低了用户的认知成本。

  @Builder
  qaCard(item: QaItem) {
    Column() {
      Text(item.question).fontSize(15).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
        .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }).width('100%')
      Text('#' + item.tag).fontSize(11).fontColor(COLORS.blue)
        .backgroundColor(COLORS.chip).padding({ left: 8, right: 8, top: 3, bottom: 3 })
        .borderRadius(4).margin({ top: 6 })

问答卡片采用纵向布局,最上方是问题标题,使用 15px 中等字重,最多显示两行。问题标题是问答卡片最重要的信息,因此字号最大、颜色最深,占据视觉主导地位。

标题下方是问题标签,以"#标签名"的形式展示。标签使用蓝色文字配浅蓝色背景的 Chip 样式,既突出了标签的分类属性,又与整体蓝色主题相协调。标签功能帮助用户快速识别问题的领域分类,也便于按标签筛选相关问题。

      Row() {
        Text('💬 ').fontSize(12)
        Text(item.bestAnswer).fontSize(12).fontColor(COLORS.sub)
          .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis }).layoutWeight(1)
      }.width('100%').padding(10).backgroundColor(COLORS.bg).borderRadius(8).margin({ top: 8 })

这是问答卡片最具特色的部分——最佳回答预览区。与普通问答列表只显示问题标题不同,这里专门开辟了一块区域展示最佳回答的内容预览。预览区使用淡蓝色背景(COLORS.bg)与卡片主体的白色背景形成区分,对话气泡 Emoji 💬 进一步强化了"回答"的语义。

最佳回答预览最多显示两行,超出部分省略。用户在列表浏览时,不仅能看到问题,还能快速获取答案要点,这极大地提升了信息密度和浏览效率。对于求知欲强的学习者来说,这种"问题+答案摘要"的双层结构比单纯的问题列表更有价值。

      Row() {
        Text('👤' + item.asker).fontSize(12).fontColor(COLORS.text3)
        Text(' 💬' + item.answers).fontSize(12).fontColor(COLORS.text3).margin({ left: 10 })
        Text(' 👍' + item.likes).fontSize(12).fontColor(COLORS.text3).margin({ left: 10 })
        Text('详情>').fontSize(12).fontColor(COLORS.blue).margin({ left: 'auto' })
          .onClick(() => { this.selectedId = QA_LIST.indexOf(item); this.showEdit = true; })
      }.width('100%').margin({ top: 8 })
    }
    .width('100%').padding(14).backgroundColor(COLORS.card).borderRadius(12)
    .onClick(() => { this.selectedId = QA_LIST.indexOf(item); this.showEdit = true; })
  }

卡片底部是元数据行,从左到右依次展示提问者、回答数、点赞数。这些数据反映了问题的热度和质量:回答数多说明问题受关注,点赞数高说明问题质量好。三者均使用浅灰色小字,作为辅助信息不抢主内容的风头。

最右侧是"详情>"链接,点击可进入问题详情页。整卡也支持点击,点击效果与"详情"链接一致,提供了更大的点击热区,提升了操作便捷性。

十四、Tab5 我的模块:学习数据中心与个人主页

"我的"模块是用户的个人数据中心,集中展示学习成果、学习记录、排行榜、功能入口等个人化信息。tabMine Builder 函数构建了一个内容丰富、层次分明的个人中心页面,包含个人信息、学习数据、学习日历、时长图表、学习排行榜和功能列表六大板块。

个人信息卡

  @Builder
  tabMine() {
    Column({ space: 12 }) {
      Row() {
        Text(this.student.avatar).fontSize(44).width(60).height(60).textAlign(TextAlign.Center)
          .backgroundColor(COLORS.chip).borderRadius(30).margin({ right: 14 })
        Column() {
          Text(this.student.name).fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
          Text('学海无涯,勤能补拙').fontSize(12).fontColor(COLORS.sub).margin({ top: 4 })
        }.layoutWeight(1).alignItems(HorizontalAlign.Start)
        Text('⚙️').fontSize(20).onClick(() => { this.showEdit = true; })
      }.width('100%').padding(16).backgroundColor(COLORS.card).borderRadius(12)

个人信息卡是"我的"页面的"门面",采用横向布局。左侧是 60px 的圆形头像,头像使用 Emoji 字符,背景为浅蓝色。中间是用户昵称和个性签名,昵称加粗突出,个性签名"学海无涯,勤能补拙"传递积极向上的学习态度。右侧是齿轮图标,作为设置入口。

学习数据四宫格

      Column() {
        Text('学习数据').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
          .width('100%').margin({ bottom: 12 })
        Row() {
          this.dataItem(this.student.studyDays.toString(), '学习天数', '📅')
          this.dataItem(formatTime(this.student.totalHours), '总时长', '⏱')
        }.width('100%')
        Row() {
          this.dataItem(this.student.coursesDone.toString(), '完成课程', '📚')
          this.dataItem(this.student.certs.toString(), '获得证书', '🏆')
        }.width('100%').margin({ top: 12 })
      }.width('100%').padding(16).backgroundColor(COLORS.card).borderRadius(12)

学习数据板块以 2x2 的宫格形式展示四项核心学习指标:学习天数、总时长、完成课程数、获得证书数。每项数据都通过 dataItem Builder 函数渲染,保证了样式的一致性。

  @Builder
  dataItem(value: string, label: string, icon: string) {
    Column() {
      Text(icon).fontSize(22)
      Text(value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ top: 4 })
      Text(label).fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
    }.layoutWeight(1).alignItems(HorizontalAlign.Center)
  }

dataItem 函数接收三个参数:数值、标签和图标。内容垂直排列,从上到下依次是图标、数值、标签,字号递减、权重递减、颜色由深到浅,形成清晰的三级视觉层级。layoutWeight(1) 确保两个数据项平分宽度。

学习日历

      Column() {
        Row() {
          Text('学习日历').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
          Text(' 近28天').fontSize(12).fontColor(COLORS.sub)
        }.width('100%').margin({ bottom: 10 })
        Column({ space: 5 }) {
          ForEach([0, 1, 2, 3], (row: number) => {
            Row() {
              ForEach([0, 1, 2, 3, 4, 5, 6], (col: number) => {
                this.calendarDot(CALENDAR_DATA[row * 7 + col])
              }, (col: number) => 'cd_' + row + '_' + col)
            }.width('100%').justifyContent(FlexAlign.SpaceBetween)
          }, (row: number) => 'row_' + row)
        }.width('100%')

学习日历是个人中心的一个趣味功能,以点阵图的形式展示近 28 天的学习打卡情况。日历采用 4 行 7 列的网格布局(4 周 × 7 天),共 28 个圆点。

实现方式使用双层 ForEach 循环:外层循环 4 行,内层循环 7 列。每个圆点通过 row * 7 + col 的索引公式从 CALENDAR_DATA 数组中获取对应日期的学习状态(1 表示已学习,0 表示未学习)。

  @Builder
  calendarDot(learned: number) {
    Circle({ width: 26, height: 26 })
      .fill(learned === 1 ? COLORS.blue : COLORS.line)
      .opacity(learned === 1 ? 1 : 0.5)
  }

calendarDot 函数渲染单个日历圆点,使用 Circle 形状组件。已学习的日期填充教育蓝、不透明度为 1,未学习的日期填充浅灰色、不透明度为 0.5。通过颜色和透明度的双重差异,学习日与非学习日形成鲜明对比,用户一眼就能看出自己的学习规律与坚持程度。

        Row() {
          Row() {
            Circle({ width: 9, height: 9 }).fill(COLORS.blue).margin({ right: 4 })
            Text('已学习').fontSize(11).fontColor(COLORS.sub)
          }.margin({ right: 20 })
          Row() {
            Circle({ width: 9, height: 9 }).fill(COLORS.line).margin({ right: 4 })
            Text('未学习').fontSize(11).fontColor(COLORS.sub)
          }
        }.width('100%').margin({ top: 10 })
      }.width('100%').padding(16).backgroundColor(COLORS.card).borderRadius(12)

日历下方配有图例,说明蓝色圆点和灰色圆点分别代表已学习和未学习。图例虽然简单,但对于新用户理解日历含义很有帮助,体现了设计的周全性。

学习排行榜

      Column() {
        Row() {
          Text('🏆 学习排行榜').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
          Text('本周榜').fontSize(12).fontColor(COLORS.orange)
            .backgroundColor(COLORS.chip).padding({ left: 8, right: 8, top: 2, bottom: 2 }).borderRadius(4)
            .margin({ left: 8 })
          Text('全部 >').fontSize(12).fontColor(COLORS.blue).margin({ left: 'auto' })
        }.width('100%').margin({ bottom: 12 })

        Column({ space: 8 }) {
          ForEach(RANK_LIST, (stu: StudentItem, index: number) => {
            this.rankItem(stu, index)
          }, (stu: StudentItem) => stu.name)
        }.width('100%')
      }.width('100%').padding(16).backgroundColor(COLORS.card).borderRadius(12)

学习排行榜是激发用户学习动力的重要功能,利用人的竞争心理促进学习行为。标题行带有奖杯 Emoji 和"本周榜"标签,明确排行榜的时间范围。

  @Builder
  rankItem(stu: StudentItem, index: number) {
    Row() {
      Text(index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : (index + 1).toString())
        .fontSize(index < 3 ? 22 : 16)
        .fontColor(index < 3 ? COLORS.orange : COLORS.text3)
        .fontWeight(FontWeight.Bold)
        .width(36).textAlign(TextAlign.Center)

rankItem 函数构建单条排行记录。最左侧是排名显示,前三名使用金银铜牌 Emoji,字号更大(22px)、颜色为活力橙色,突出前三名的荣誉感;第四名及以后使用数字排名,字号较小(16px)、颜色为灰色,视觉权重降低。这种差异化设计强化了前三名的成就感,也符合用户对排行榜的认知习惯。

      Text(stu.avatar).fontSize(28).width(40).height(40).textAlign(TextAlign.Center)
        .backgroundColor(COLORS.chip).borderRadius(20).margin({ left: 8, right: 12 })
      Column() {
        Text(stu.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(COLORS.title)
        Text('学习' + stu.studyDays + '天 · ' + formatTime(stu.totalHours))
          .fontSize(11).fontColor(COLORS.text3).margin({ top: 2 })
      }.layoutWeight(1).alignItems(HorizontalAlign.Start)
      if (index < 3) {
        Text('🏅').fontSize(18)
      }
    }
    .width('100%').height(56).padding({ left: 4, right: 4 })
    .backgroundColor(index === 0 ? 'rgba(249,115,22,0.06)' : COLORS.bg)
    .borderRadius(8)
    .onClick(() => { this.showEdit = true; })
  }

中间部分是用户头像和学习信息,右侧前三名额外显示一枚勋章 Emoji 🏅。第一名的行背景使用淡橙色(rgba(249,115,22,0.06)),在视觉上突出冠军位置,其余名次使用淡蓝色背景。这种背景色的微妙差异进一步强化了排名的层级感。

功能列表

      Column() {
        this.menuItem('📋', '我的订单', () => { this.showEdit = true; })
        this.menuItem('🎁', '我的收藏', () => { this.showEdit = true; })
        this.menuItem('📜', '学习证书', () => { this.showDel = true; })
        this.menuItem('💬', '意见反馈', () => { this.showAdd = true; })
        this.menuItem('ℹ️', '关于我们', () => { this.showEdit = true; })
      }.width('100%').backgroundColor(COLORS.card).borderRadius(12).overflow(Overflow.Hidden)
    }.width('100%')
  }

页面底部是功能列表菜单,包含我的订单、我的收藏、学习证书、意见反馈、关于我们五个入口。每个菜单项通过 menuItem 函数构建,统一的样式确保了视觉一致性。

  @Builder
  menuItem(icon: string, title: string, click: () => void) {
    Row() {
      Text(icon).fontSize(18).margin({ right: 12 })
      Text(title).fontSize(14).fontColor(COLORS.title).layoutWeight(1)
      Text('>').fontSize(14).fontColor(COLORS.text3)
    }.width('100%').height(48).padding({ left: 16, right: 16 })
      .onClick(() => { click(); })
  }

menuItem 函数采用经典的列表行设计:左侧图标 + 中间标题 + 右侧箭头。函数接收点击回调作为参数,将点击事件的处理逻辑委托给调用方,实现了行为与表现的分离。这种设计模式使得 menuItem 函数具有高度的通用性,可以复用于任何列表菜单场景。

值得注意的是菜单容器设置了 overflow(Overflow.Hidden),配合圆角使用,确保子元素的直角边缘不会超出圆角容器。这是 ArkUI 中实现圆角裁剪的常用技巧。

十五、图表卡片:纯组件构建的柱状图

数据可视化是个人中心页面的重要组成部分,一张直观的图表胜过千言万语。当前页面的学习时长趋势图表并未引入第三方图表库,而是完全通过 ArkUI 基础组件的组合构建而成,展现了声明式 UI 在自定义可视化方面的强大表现力。

  @Builder
  chartCard() {
    Column() {
      Row() {
        Text('📊 学习时长趋势').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
        Text('近6个月').fontSize(12).fontColor(COLORS.sub).margin({ left: 'auto' })
      }.width('100%').margin({ bottom: 14 })

图表卡片的标题行简洁明了:左侧是带图表 Emoji 的标题,右侧是时间范围说明。标题与辅助说明的字号、颜色、字重差异,形成了清晰的信息层级。

      Row() {
        ForEach(STUDY_VAL, (val: number, index: number) => {
          Column() {
            Column() {
              Text(val + 'h').fontSize(10).fontColor(COLORS.blue).fontWeight(FontWeight.Medium).margin({ bottom: 4 })
            }
            .width('100%').height((val / CHART_MAX) * 110)
            .backgroundColor(COLORS.blue).borderRadius({ topLeft: 4, topRight: 4 })
            .opacity(index === 5 ? 1 : 0.7)
            Text(MONTHS[index]).fontSize(11).fontColor(COLORS.sub).margin({ top: 6 })
          }.layoutWeight(1).alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.End)
        }, (val: number, index: number) => 'chart_' + index)
      }.width('100%').height(150).alignItems(VerticalAlign.Bottom)
    }.width('100%').padding(16).backgroundColor(COLORS.card).borderRadius(12)
  }

柱状图的核心实现思路非常巧妙。外层是一个固定高度 150px 的 Row 容器,设置 alignItems(VerticalAlign.Bottom) 使子元素底部对齐——这是柱状图"从下往上生长"的关键。

内层通过 ForEach 遍历学习时长数据 STUDY_VAL,每个数据项对应一根柱子。每根柱子由三部分组成:顶部的数值标签、中间的柱体、底部的月份标签。

柱体的高度通过比例计算动态得出:(val / CHART_MAX) * 110。其中 CHART_MAX 是数据最大值加 10 得到的图表上限,110 是柱体区域的最大可用高度(总高度 150 减去数值标签和月份标签所占空间)。这种等比例计算确保了所有柱子在视觉上的高低关系与数据大小完全一致。

柱体颜色使用教育蓝,顶部两个圆角 4px,形成圆润的柱顶造型。透明度设置上,最新月份(index === 5,即 8 月)的不透明度为 1,其他月份为 0.7,通过透明度差异突出最新数据,引导用户关注近期趋势。

每根柱子的宽度通过 layoutWeight(1) 平均分配,确保 6 根柱子在水平方向上等宽排列。justifyContent(FlexAlign.End) 配合底部对齐,使得柱子从底部向上"生长"。

这种纯组件实现的柱状图虽然功能相对简单(不支持交互、动画效果有限),但在展示静态趋势数据的场景下已经完全够用。与引入第三方图表库相比,它具有以下优势:零依赖、包体积小、渲染性能高、样式完全可控、与主题色无缝融合。对于大多数业务场景中的简单图表需求,这种"自己动手"的实现方式往往是更优的选择。

十六、底部 Tab 栏:导航枢纽与微动效

底部 Tab 栏是整个应用的导航枢纽,用户通过它在六大功能模块之间切换。tabBar Builder 函数构建了一个简洁而富有动效的底部导航栏。

  @Builder
  tabBar() {
    Row() {
      ForEach(TAB_LIST, (tab: TabMeta) => {
        Column() {
          Text(tab.icon).fontSize(20)
            .scale({ x: this.currentTab === tab.index ? 1.2 : 1.0, y: this.currentTab === tab.index ? 1.2 : 1.0 })
            .animation({ duration: 200 })
          Text(tab.name).fontSize(11)
            .fontColor(this.currentTab === tab.index ? COLORS.tabOn : COLORS.text3)
            .fontWeight(this.currentTab === tab.index ? FontWeight.Medium : FontWeight.Normal)
            .margin({ top: 2 })
        }.layoutWeight(1).height(56).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
          .onClick(() => { this.currentTab = tab.index; })
      }, (tab: TabMeta) => tab.index.toString())
    }.width('100%').backgroundColor(COLORS.card)
    .shadow({ radius: 8, color: 'rgba(0,0,0,0.05)', offsetY: -2 })
  }

底部 Tab 栏的实现采用了数据驱动的设计模式:通过遍历 TAB_LIST 元数据数组动态生成 Tab 项,而非硬编码六个 Tab。这种方式的好处是 Tab 的数量、名称、图标都可以通过修改数据配置来调整,无需改动渲染逻辑,具有良好的可扩展性。

每个 Tab 项使用 Column 垂直布局,上方是图标 Emoji,下方是文字名称。Tab 项的宽度通过 layoutWeight(1) 平均分配,确保六个 Tab 在底部栏中等宽排列。高度为 56px,符合移动端导航栏的标准尺寸规范。

Tab 的选中状态通过 currentTab === tab.index 的条件判断动态呈现,具体体现在三个维度的差异上。其一,图标缩放:选中状态下图标放大至 1.2 倍,未选中状态保持 1.0 倍原始大小。放大效果通过 scale 属性实现,并配合 animation 属性添加 200ms 的过渡动画,使缩放过程平滑自然。这种"选中放大"的微动效能够给用户明确的视觉反馈,增强交互的愉悦感。

其二,文字颜色:选中状态文字为教育蓝(COLORS.tabOn),未选中状态为浅灰色(COLORS.text3)。颜色切换是最基础也是最有效的选中态标识方式。

其三,字重变化:选中状态字重为 Medium(中等),未选中状态为 Normal(常规)。字重的微妙变化进一步强化了选中项的视觉权重,让用户无需仔细辨认颜色也能快速定位当前所在模块。

点击 Tab 项时,只需将 currentTab 状态变量更新为对应索引即可。由于 ArkUI 的响应式机制,currentTab 的变化会自动触发两处视图更新:一是底部 Tab 栏的选中态变化,二是内容区通过 if-else if 切换渲染对应的 Tab 内容。状态变量成为连接视图与交互的桥梁,这正是声明式 UI “数据驱动视图” 思想的精髓所在。

底部 Tab 栏的顶部添加了一道淡淡的阴影:模糊半径 8px,颜色为 5% 透明度的黑色,Y 轴偏移 -2px(即阴影向上投射)。这道阴影在白色 Tab 栏与下方内容之间形成了微妙的视觉分离,增强了 Tab 栏的层次感,也符合 Material Design 中底部导航栏的设计规范。

十七、弹窗系统:遮罩与三面板架构

弹窗是移动应用中常见的交互模式,用于处理新增、编辑、删除等需要用户聚焦操作的场景。当前页面的弹窗系统采用遮罩层 + 功能面板的经典架构,共包含一个遮罩层和三个功能面板,统一由状态变量控制显隐。

遮罩层

  @Builder
  modalOverlay() {
    Stack({ alignContent: Alignment.Center }) {
      Column()
        .width('100%').height('100%').backgroundColor(COLORS.mask)
        .onClick(() => { this.showAdd = false; this.showEdit = false; this.showDel = false; })
    }.width('100%').height('100%').position({ x: 0, y: 0 }).zIndex(10)
  }

遮罩层是弹窗系统的基础设施,它有两个核心作用。第一是视觉聚焦:通过半透明黑色背景(COLORS.mask,即 50% 透明度的黑色)弱化底层内容,将用户的注意力引导到弹窗面板上。第二是交互拦截:点击遮罩层可以关闭弹窗,提供了便捷的"取消"操作方式。

遮罩层使用 position({ x: 0, y: 0 }) 定位到屏幕左上角,并设置 zIndex(10) 确保其位于主内容之上。内部使用 Stack 容器居中对齐,为后续的弹窗面板提供居中定位的基准。点击遮罩层时,将三个弹窗状态变量全部设为 false,确保无论当前打开的是哪种弹窗都能关闭。

添加面板

  @Builder
  panelAdd() {
    Column() {
      Text('➕ 添加课程').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ bottom: 16 })
      TextInput({ placeholder: '课程名称' })
        .width('100%').height(42).backgroundColor(COLORS.bg).borderRadius(8)
        .padding({ left: 12, right: 12 }).placeholderColor(COLORS.text3)
      TextInput({ placeholder: '讲师姓名' })
        .width('100%').height(42).backgroundColor(COLORS.bg).borderRadius(8)
        .padding({ left: 12, right: 12 }).placeholderColor(COLORS.text3).margin({ top: 10 })
      TextInput({ placeholder: '课时数量' })
        .width('100%').height(42).backgroundColor(COLORS.bg).borderRadius(8)
        .padding({ left: 12, right: 12 }).placeholderColor(COLORS.text3).margin({ top: 10 })
      TextInput({ placeholder: '课程价格' })
        .width('100%').height(42).backgroundColor(COLORS.bg).borderRadius(8)
        .padding({ left: 12, right: 12 }).placeholderColor(COLORS.text3).margin({ top: 10 })

添加面板用于新增课程信息,面板内包含四个输入框:课程名称、讲师姓名、课时数量、课程价格。每个输入框高度 42px,背景为淡蓝色(COLORS.bg),圆角 8px,内边距 12px,视觉风格统一且舒适。

输入框的提示文字(placeholder)使用浅灰色(COLORS.text3),与白色输入文字形成对比,既可见又不会干扰用户输入。输入框之间通过 10px 的上边距分隔,间距适中,既不会显得拥挤也不会过于松散。

      Row() {
        Button('取消').layoutWeight(1).height(42).backgroundColor(COLORS.bg).fontColor(COLORS.sub)
          .borderRadius(8).margin({ right: 10 }).onClick(() => { this.showAdd = false; })
        Button('确定添加').layoutWeight(1).height(42).backgroundColor(COLORS.blue).fontColor(COLORS.card)
          .borderRadius(8).onClick(() => { this.showAdd = false; })
      }.width('100%').margin({ top: 20 })
    }.width('85%').padding(20).backgroundColor(COLORS.card).borderRadius(16).zIndex(20)
  }

面板底部是操作按钮区,左右两个按钮平分宽度(各占 50%)。左侧"取消"按钮为浅灰蓝背景配灰色文字,视觉权重较低,作为次要操作;右侧"确定添加"按钮为教育蓝背景配白色文字,视觉权重较高,作为主要操作。主次按钮的色彩区分符合用户的操作预期——蓝色按钮代表"确认/前进",灰色按钮代表"取消/返回"。

面板整体宽度为屏幕的 85%,圆角 16px,内边距 20px,zIndex 设为 20(高于遮罩层的 10),确保面板显示在遮罩层之上。

编辑面板

  @Builder
  panelEdit() {
    Column() {
      Text('✏️ 编辑信息').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ bottom: 16 })
      TextInput({ placeholder: '请输入名称' })
        .width('100%').height(42).backgroundColor(COLORS.bg).borderRadius(8)
        .padding({ left: 12, right: 12 }).placeholderColor(COLORS.text3)
      TextArea({ placeholder: '请输入描述信息...' })
        .width('100%').height(90).backgroundColor(COLORS.bg).borderRadius(8)
        .padding(12).placeholderColor(COLORS.text3).margin({ top: 10 })

编辑面板与添加面板结构相似,但有两处不同。一是标题不同,编辑面板显示"✏️ 编辑信息",明确当前操作性质。二是表单内容不同,编辑面板只有名称输入框和描述文本域两个字段,且第二个字段使用 TextArea 多行文本输入组件,高度为 90px,支持多行文本输入。

这种设计体现了不同场景不同表单的思路——添加时需要录入完整信息,编辑时可能只需要修改核心字段。在实际项目中,编辑面板的输入框应该预填充当前选中项的数据,这里因为是演示版本所以使用了 placeholder 占位。

      Row() {
        Button('取消').layoutWeight(1).height(42).backgroundColor(COLORS.bg).fontColor(COLORS.sub)
          .borderRadius(8).margin({ right: 10 }).onClick(() => { this.showEdit = false; })
        Button('保存修改').layoutWeight(1).height(42).backgroundColor(COLORS.orange).fontColor(COLORS.card)
          .borderRadius(8).onClick(() => { this.showEdit = false; })
      }.width('100%').margin({ top: 20 })
    }.width('85%').padding(20).backgroundColor(COLORS.card).borderRadius(16).zIndex(20)
  }

编辑面板的"保存修改"按钮使用活力橙色,与添加面板的蓝色主按钮形成区分。这是一个有趣的设计细节——添加操作用蓝色(新建/创建),编辑操作用橙色(修改/变更),色彩语义与操作性质相呼应。

删除确认面板

  @Builder
  panelDel() {
    Column() {
      Text('⚠️ 确认删除').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ bottom: 10 })
      Text('确定要删除这条记录吗?删除后无法恢复,请谨慎操作。')
        .fontSize(14).fontColor(COLORS.sub).textAlign(TextAlign.Center).width('100%')

删除面板与前两种面板风格截然不同。它没有表单输入区域,而是一个确认对话框——用于在执行不可逆的删除操作前,向用户进行二次确认,防止误操作。

面板顶部是警告图标加标题"确认删除",下方是提示文案,提醒用户删除操作的不可逆性。提示文字使用居中对齐,字号 14px,灰色显示,语气恳切而谨慎。

      Row() {
        Button('取消').layoutWeight(1).height(42).backgroundColor(COLORS.bg).fontColor(COLORS.sub)
          .borderRadius(8).margin({ right: 10 }).onClick(() => { this.showDel = false; })
        Button('确认删除').layoutWeight(1).height(42).backgroundColor(COLORS.red).fontColor(COLORS.card)
          .borderRadius(8).onClick(() => { this.showDel = false; })
      }.width('100%').margin({ top: 20 })
    }.width('85%').padding(20).backgroundColor(COLORS.card).borderRadius(16).zIndex(20)
  }

删除面板的主操作按钮使用红色背景,这是三种弹窗中唯一使用红色的。红色在色彩心理学中代表危险、警告、停止,用于删除操作非常贴切——它能有效唤起用户的警觉,确保用户是在明确知晓后果的前提下做出删除决定。

弹窗系统设计总结

三种弹窗面板虽然功能不同,但共享一致的视觉规范:85% 宽度、16px 圆角、20px 内边距、zIndex 为 20、底部双按钮布局。这种统一性确保了用户在不同弹窗场景下都能获得一致的交互体验,降低了学习成本。

从架构角度看,弹窗系统的设计非常清晰。一个遮罩层 + 多个功能面板的模式,通过布尔状态变量控制显隐,是弹窗系统的经典实现方式。它的优势在于:各面板独立、互不干扰;添加新的弹窗类型只需新增一个状态变量和一个 Builder 函数;遮罩逻辑统一维护,不需要重复实现。

十八、功能模块对比表

为了更直观地对比六大功能模块的差异与特点,下面从多个维度进行系统对比,帮助读者快速把握各模块的核心特征与设计思路。

对比维度 课程模块 学习模块 考试模块 题库模块 社区模块 我的模块
核心功能 课程浏览与发现 学习进度追踪 考试安排与成绩 刷题练习 问答交流 个人数据中心
数据模型 CourseItem LearnItem ExamItem QuestionItem QaItem StudentItem
布局形态 双列卡片瀑布流 横向列表卡片 横向列表卡片 横向列表卡片 纵向问答卡片 多板块纵向堆叠
进度条类型 环形+线性双形态 线性(颜色随正确率变)
主操作色 教育蓝 教育蓝 教育蓝 活力橙 教育蓝 教育蓝
特色组件 呼吸动效图标、分类宫格 双进度条 四色统计卡片 正确率色彩分级 最佳回答预览 日历点阵图+柱状图+排行榜
卡片交互 加入课程按钮 点击进入学习 状态驱动按钮文案 练习按钮 点击查看详情 多项功能入口
信息密度 高(双列+多信息) 中高 高(问题+答案预览) 极高(多板块聚合)
情感化设计 呼吸动效吸引注意 进度反馈激励坚持 考试须知人文关怀 正确率颜色激励提升 最佳回答高效获信息 排行榜激发竞争欲
数据字段数 6个 5个 5个 5个 6个 6个

通过上表的横向对比,可以清晰地看到各模块的定位差异与设计侧重。

课程模块以"发现"为核心目标,采用双列卡片布局最大化信息密度,呼吸动效吸引用户注意力,分类宫格提供多维度浏览入口。学习模块以"追踪"为核心,双形态进度条直观展示学习进展,进度反馈激励用户持续学习。

考试模块以"管理"为核心,四色统计卡片一目了然,状态驱动的按钮设计确保不同考试阶段有对应的操作入口。题库模块以"练习"为核心,正确率色彩分级将学习质量可视化,橙色主题强化行动号召。

社区模块以"交流"为核心,最佳回答预览提升信息密度,帮助用户快速获取知识。我的模块以"汇总"为核心,聚合了个人信息、学习数据、日历、图表、排行榜等多种元素,是用户学习成果的集中展示窗口。

六个模块虽然各自独立,但共享统一的色彩体系、卡片样式、间距规范和交互模式,形成了完整而和谐的产品体验。

十九、总结与展望

源码设计亮点总结

通过对该源码的全面深度剖析,我们可以总结出以下几方面的设计亮点与技术价值。

其一,声明式 UI 范式的纯熟运用。 整页代码完全遵循 ArkUI 声明式开发范式,通过 @Component 自定义组件、@Builder UI 片段复用、@State 状态驱动等核心机制,构建了结构清晰、逻辑直观的界面代码。开发者只需描述"UI 应该是什么样",框架自动处理"如何更新",大幅提升了开发效率与代码可维护性。尤其是 @Builder 函数的大量使用,将 UI 片段按功能模块拆分,每个函数职责单一、命名语义化,代码可读性极强。

其二,色彩体系的系统化设计。 源码通过 ColorPalette 接口与 COLORS 常量构建了一套完整的色彩系统,涵盖背景色、文字色、主题色、功能色四大类共 14 个色值。色彩不仅在视觉上美观和谐,更承载了丰富的语义信息——蓝色代表主操作与主题、橙色代表行动与强调、绿色代表成功与完成、红色代表警告与危险。色彩语义的一致性贯穿所有模块,用户在使用过程中会潜移默化地形成色彩认知,从而提升使用效率。

其三,数据模型的精雕细琢。 六个 @Observed 数据模型类虽然代码量不大,但每个字段都经过精心设计,准确对应业务需求。模型类的设计遵循了单一职责原则——每个模型对应一个业务实体,字段数量控制在 5-6 个,既完整覆盖业务信息,又不至于过于臃肿。@Observed 装饰器的使用确保了数据变更能够自动驱动视图更新,是 ArkUI 响应式编程的基础。

其四,动效设计的恰到好处。 页面中的动效不多但每一处都有其存在的意义。课程卡片的呼吸动画为静态界面注入了生命力,吸引用户对课程内容的关注;Tab 切换的图标缩放动效为导航交互提供了即时反馈;柱状图的透明度差异突出了最新数据。这些微动效都遵循了"克制而有效"的原则——不炫技、不干扰,只为提升用户体验而存在。

其五,模块化架构的清晰分层。 从整体架构来看,页面呈现出清晰的分层结构:数据模型层、工具函数层、视图组件层、弹窗系统层各司其职、边界清晰。Builder 函数按功能模块划分,每个模块内部又按子组件进一步拆分。这种模块化架构使得代码易于理解、便于维护,也为团队协作开发提供了良好的基础。

技术展望与优化方向

站在更高的视角审视,该源码作为一个演示版本已经相当出色,但在实际生产环境中应用时,还有以下几个方面可以进一步优化与拓展。

状态管理方面,当前页面的状态全部通过 @State 变量在组件内部管理,对于单页面应用来说已经足够。但如果应用规模扩大、页面增多、需要跨页面共享数据时,可以考虑引入 AppStorage 全局存储、@Link 父子组件联动,或者集成第三方状态管理库,实现更系统化的状态管理。

数据来源方面,当前使用的是 Mock 硬编码数据。在实际项目中,需要将数据层替换为网络请求。可以封装统一的 HTTP 请求工具,结合 async/await 异步语法,并添加加载状态、错误处理、缓存机制等,构建健壮的数据获取层。数据模型类也可以扩展 fromJSONtoJSON 等序列化方法,便于与后端接口对接。

组件复用方面,当前页面的 Builder 函数已经实现了相当程度的 UI 复用,但这些函数都定义在同一个组件内部,只能在当前页面使用。如果多个页面需要共用某些组件(如课程卡片、学习进度条等),可以将它们抽离为独立的自定义组件文件,通过 @Component 装饰器导出,在需要的页面引入使用。这样可以进一步提升代码复用率,也符合组件化开发的最佳实践。

性能优化方面,对于长列表场景,可以考虑使用 LazyForEach 懒加载替代 ForEach 全量渲染,通过提供数据源实现按需加载,大幅提升长列表的滑动性能。此外,cachedCount 缓存机制、组件复用、减少不必要的状态更新等,都是 ArkUI 性能优化的常用手段。

功能拓展方面,六大模块目前都只是列表展示层面的功能,每个模块都可以向纵深方向拓展。课程模块可以增加搜索、筛选、排序功能;学习模块可以增加视频播放器、课程目录、笔记功能;考试模块可以增加答题界面、计时器、成绩分析;题库模块可以增加答题模式、错题本、收藏题;社区模块可以增加发帖、评论、点赞、关注;我的模块可以增加消息中心、设置页面、会员系统等。

多端适配方面,HarmonyOS 的一大优势是一次开发多端部署。当前页面主要面向手机端设计,未来可以通过响应式布局、断点适配等技术,拓展支持平板、折叠屏、车机、智能手表等多种设备形态,充分发挥 HarmonyOS 分布式能力的优势。

结语

"学无涯"在线教育应用的当前页面,虽然只是一个前端界面演示,但它所展现出的设计思想与技术实现,具有很高的参考价值。从系统化的色彩设计到数据驱动的 Tab 导航,从双形态进度条到正确率色彩分级,从纯组件实现的柱状图到精心设计的弹窗系统,每一处细节都体现了开发者对用户体验的深入思考和对 ArkUI 框架的熟练掌握。

在 HarmonyOS 生态蓬勃发展的今天,掌握 ArkUI 声明式开发技术,对于每一位移动端开发者来说都具有重要意义。希望本文的深度剖析能够帮助读者更好地理解 ArkUI 的开发范式与设计模式,在实际项目中构建出更加优秀的 HarmonyOS 应用。

学海无涯,勤能补拙。愿每一位在技术道路上不断求索的开发者,都能在编程的世界中找到属于自己的星辰大海。

附录:DevEco Studio 创建新项目与查看 SDK 版本

本章节演示如何使用 DevEco Studio 创建一个 HarmonyOS 新项目,并查看当前 IDE 已安装的 SDK 版本,适合作为其他技术博文的补充操作指南。


一、创建新项目

1.1 进入欢迎界面

启动 DevEco Studio 后,首先看到的是欢迎界面。左侧导航栏默认选中 “项目”,右侧提供三个主要入口:

  • 新建项目:从头创建新项目
  • 打开项目:打开本地已有项目
  • 克隆仓库:从 Git 等版本控制拉取代码

点击 “新建项目” 按钮,进入项目创建向导。

在这里插入图片描述

1.2 选择项目模板

在弹出的"新建项目"对话框中,左侧分类标签提供了两种项目类型:

类型 说明
应用(Application) 开发标准的 HarmonyOS 应用,具备完整的 Ability 生命周期
元服务(Atomic Service) 开发轻量级的原子化服务,无需安装即可使用

选择 “应用” 标签后,右侧展示多种模板。对于大多数场景,推荐选择 “Empty Ability” —— 这是一个最基础的入门模板,仅包含 Hello World 功能,适合从零开始构建应用。

在这里插入图片描述

1.3 配置项目信息

点击 “下一步” 后,进入项目配置界面,需要填写以下核心参数:

配置项 示例值 说明
项目名称(Project name) rollboat 应用的项目名称,建议使用英文命名
包名(Bundle name) com.rollboat.myapplication 应用唯一标识,采用反向域名格式
保存路径(Save location) D:\CodeFactory\rollboat 项目本地存储路径,避免使用中文和空格
兼容 SDK(Compatible SDK) 6.1.1(24) 目标 HarmonyOS API 版本,点击"查看参考"可了解各版本差异
模块名称(Module name) entry 主模块名称,默认 entry 为应用入口模块
设备类型(Device types) ☑ Phone 勾选目标设备:Phone / Tablet / 2in1 / Car / Wearable / TV

右侧预览区会实时展示当前模板的默认效果 —— 一个居中显示的 “Hello World” 文本。

在这里插入图片描述

1.4 完成创建

确认配置无误后,点击右下角 “完成” 按钮,IDE 将自动执行以下操作:

  1. 生成项目骨架(Stage 模型目录结构)
  2. 执行 ohpm install 安装依赖
  3. 运行 Hvigor 构建初始化(Build Init

构建日志中显示 “退出代码为 0” 表示项目初始化成功。

在这里插入图片描述

1.5 项目结构概览

创建完成后,左侧项目面板展示的是标准的 Stage 模型 目录结构:

rollboat/
├── .hvigor/                   # Hvigor 构建工具缓存
├── .idea/                     # IDE 配置文件
├── AppScope/                  # 应用级全局配置
│   └── app.json5
├── entry/                     # 主模块(入口模块)
│   ├── src/main/ets/
│   │   ├── entryability/      # Ability 生命周期管理
│   │   │   └── EntryAbility.ets
│   │   └── pages/             # UI 页面
│   │       └── Index.ets      # 首页(默认 Hello World)
│   ├── src/main/resources/    # 资源文件
│   ├── module.json5           # 模块配置
│   └── build-profile.json5    # 构建配置
├── oh_modules/                # OHPM 依赖包
├── build-profile.json5        # 工程构建配置
├── hvigorfile.ts              # Hvigor 构建脚本
└── oh-package.json5           # 包管理配置

核心文件 Index.ets 的默认代码如下,采用 ArkTS 声明式 UI 语法:

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    RelativeContainer() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize($r('app.float.page_text_font_size'))
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onClick(() => {
          this.message = 'Welcome';
        })
    }
    .height('100%')
    .width('100%')
  }
}
关键语法 作用
@Entry 标记为页面入口,可用于路由跳转
@Component 声明为自定义组件
@State 状态变量,数据变更时自动触发 UI 刷新
RelativeContainer 相对布局容器,替代传统线性布局
.onClick() 点击事件,此处点击后文本变为 “Welcome”

打开右侧 Previewer(预览器),选择 Phone 设备,即可实时预览 Hello World 效果,无需连接真机或启动模拟器。

在这里插入图片描述


二、查看 SDK 版本

2.1 查看 HarmonyOS SDK

DevEco Studio 安装时已内置 HarmonyOS SDK,无需单独下载。通过以下路径查看:

文件 → 设置 → HarmonyOS SDK(或快捷键 Ctrl + Alt + S 搜索 “HarmonyOS SDK”)

在设置面板中,可以看到当前已安装的 SDK 版本信息:

名称 阶段 状态
HarmonyOS 6.1.1 Release ✅ 已安装

界面顶部提示:“HarmonyOS SDK 已经包含在 IDE,无需单独安装”,省去了手动配置 SDK 的繁琐步骤。

在这里插入图片描述

2.2 查看 ArkUI-X SDK(跨平台扩展)

如果项目需要将 ArkUI 框架扩展到多个 OS 平台(Android / iOS / OpenHarmony),还需要配置 ArkUI-X SDK。路径如下:

文件 → 设置 → 语言和框架 → ArkUI-X

在这里可以查看已安装和可选的 ArkUI-X SDK 版本:

版本 SDK 版本号 阶段 状态
API Version 24 6.1.1.100 Release ✅ 已安装
API Version 23 6.1.0.28 Beta1 未安装
API Version 22 6.0.2.112 Release 未安装

安装路径示例:D:\DevTools\ArkUI-X\sdk

说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

在这里插入图片描述


三、小结

步骤 操作 关键点
创建项目 欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成 使用 Stage 模型 + ArkTS 语言
查看 SDK 设置 → HarmonyOS SDK SDK 已内置,无需手动安装
跨平台扩展 设置 → ArkUI-X 根据需要安装对应 API 版本

至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。


本文基于 DevEco Studio 6.1.1 Release 版本编写,不同版本界面可能存在细微差异。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐