第15次:首页完整实现
经过前面的学习,我们已经开发了 HeroBanner、ModuleCard 组件以及 TutorialService、ProgressService 服务。本次课程将整合所有内容,完成首页的完整实现。
首页效果

学习目标
- 掌握页面数据加载流程
- 实现继续学习区域
- 完成推荐模块横向滚动
- 实现快捷入口功能
- 完成首页的完整开发
15.1 首页数据加载流程
加载时序
aboutToAppear()
│
├── StorageUtil.init() // 初始化存储
│
├── initTheme() // 初始化主题
│
├── TutorialService.init() // 初始化教程服务
│
├── loadModules() // 加载模块数据
│
├── loadProgress() // 加载用户进度
│
└── isLoading = false // 完成加载
实现代码
typescript
@Entry
@Component
struct Index {
@State currentTab: number = 0;
@State modules: LearningModule[] = [];
@State progress: UserProgress = DEFAULT_USER_PROGRESS;
@State isLoading: boolean = true;
@StorageLink('isDarkMode') isDarkMode: boolean = false;
aboutToAppear(): void {
this.initAndLoadData();
}
private async initAndLoadData(): Promise<void> {
try {
// 1. 初始化存储
await StorageUtil.init(getContext(this));
// 2. 初始化主题
initTheme(getContext(this));
// 3. 初始化教程服务
TutorialService.init();
// 4. 加载模块数据
this.modules = TutorialService.getAllModules();
// 5. 加载用户进度
this.progress = await ProgressService.loadProgress();
// 6. 初始化其他服务
SearchService.init(this.modules);
BadgeService.init(this.modules);
console.info('[Index] Data loaded successfully');
} catch (error) {
console.error('[Index] Failed to load data:', error);
} finally {
this.isLoading = false;
}
}
}
页面显示时刷新
typescript
onPageShow(): void {
// 从其他页面返回时刷新进度
this.refreshProgress();
}
private async refreshProgress(): Promise<void> {
try {
this.progress = await ProgressService.loadProgress();
} catch (error) {
console.error('[Index] Failed to refresh progress:', error);
}
}
15.2 加载状态处理
加载视图
typescript
@Builder
LoadingView() {
Column() {
LoadingProgress()
.width(48)
.height(48)
.color(this.isDarkMode ? '#61DAFB' : '#0077b6')
Text('加载中...')
.fontSize(14)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
条件渲染
typescript
build() {
Column() {
if (this.isLoading) {
this.LoadingView()
} else {
// 主内容
Tabs({ barPosition: BarPosition.End, index: this.currentTab }) {
// Tab 内容...
}
}
}
.width('100%')
.height('100%')
.backgroundColor(this.isDarkMode ? '#1a1a2e' : '#f8f9fa')
}
15.3 继续学习区域
功能设计
当用户有学习记录时,显示"继续学习"卡片,点击可直接跳转到上次学习的课程。
实现代码
typescript
@Builder
ContinueLearningSection() {
Column() {
Text('继续学习')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ bottom: 12 })
Row() {
Text('📖')
.fontSize(32)
Column() {
Text('上次学习')
.fontSize(14)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
Text(this.progress.currentLesson ?? '')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.margin({ left: 12 })
.layoutWeight(1)
Text('继续 →')
.fontSize(14)
.fontColor(this.isDarkMode ? '#61DAFB' : '#0077b6')
}
.width('100%')
.padding(16)
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
.borderRadius(16)
.shadow({
radius: 8,
color: this.isDarkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.05)',
offsetY: 2
})
.onClick(() => {
this.navigateToLastLesson();
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 20 })
.alignItems(HorizontalAlign.Start)
}
// 跳转到上次学习的课程
private navigateToLastLesson(): void {
const lessonId = this.progress.currentLesson;
if (lessonId) {
const moduleId = this.findModuleIdByLessonId(lessonId);
if (moduleId) {
router.pushUrl({
url: 'pages/LessonDetail',
params: { moduleId: moduleId, lessonId: lessonId }
});
}
}
}
// 根据课程 ID 查找模块 ID
private findModuleIdByLessonId(lessonId: string): string | undefined {
for (const module of this.modules) {
const found = module.lessons.find(l => l.id === lessonId);
if (found) {
return module.id;
}
}
return undefined;
}
条件显示
typescript
@Builder
HomeContent() {
Scroll() {
Column() {
// Hero Banner
HeroBanner({
completedLessons: this.progress.completedLessons.length,
totalLessons: TutorialService.getTotalLessonCount(),
streak: this.progress.learningStreak,
onDailyQuestionTap: () => {
router.pushUrl({ url: 'pages/QuizPage' });
}
})
// 快捷入口
this.QuickAccessSection()
// 继续学习(仅当有学习记录时显示)
if (this.progress.currentLesson) {
this.ContinueLearningSection()
}
// 推荐模块
this.RecommendedModulesSection()
}
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
}
15.4 快捷入口区域
功能设计
提供三个快捷入口:
- 面试题库:跳转到面试题练习
- 在线编程:跳转到代码练习
- 成品下载:跳转到示例项目下载
实现代码
typescript
@Builder
QuickAccessSection() {
Column() {
Text('快捷入口')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ bottom: 12 })
Row({ space: 12 }) {
// 面试题库
Column() {
Text('🎯')
.fontSize(32)
Text('面试题库')
.fontSize(13)
.fontWeight(FontWeight.Medium)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ top: 6 })
Text('海量面试真题')
.fontSize(11)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 2 })
}
.layoutWeight(1)
.padding(16)
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
.borderRadius(16)
.shadow({
radius: 8,
color: this.isDarkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.05)',
offsetY: 2
})
.onClick(() => {
router.pushUrl({ url: 'pages/QuizBankPage' });
})
// 在线编程
Column() {
Text('💻')
.fontSize(32)
Text('在线编程')
.fontSize(13)
.fontWeight(FontWeight.Medium)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ top: 6 })
Text('实战代码练习')
.fontSize(11)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 2 })
}
.layoutWeight(1)
.padding(16)
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
.borderRadius(16)
.shadow({
radius: 8,
color: this.isDarkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.05)',
offsetY: 2
})
.onClick(() => {
router.pushUrl({ url: 'pages/CodePlayground' });
})
// 成品下载
Column() {
Text('📦')
.fontSize(32)
Text('成品下载')
.fontSize(13)
.fontWeight(FontWeight.Medium)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ top: 6 })
Text('11个示例项目')
.fontSize(11)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 2 })
}
.layoutWeight(1)
.padding(16)
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
.borderRadius(16)
.shadow({
radius: 8,
color: this.isDarkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.05)',
offsetY: 2
})
.onClick(() => {
router.pushUrl({ url: 'pages/DemoDownloadPage' });
})
}
.width('100%')
}
.width('100%')
.padding({ left: 16, right: 16, top: 20, bottom: 20 })
.alignItems(HorizontalAlign.Start)
}
15.5 推荐模块横向滚动
功能设计
展示前 5 个推荐模块,支持横向滚动浏览。
实现代码
typescript
@Builder
RecommendedModulesSection() {
Column() {
Row() {
Text('推荐模块')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
Blank()
Text('查看全部 →')
.fontSize(14)
.fontColor(this.isDarkMode ? '#61DAFB' : '#0077b6')
.onClick(() => {
this.currentTab = 1; // 切换到课程 Tab
})
}
.width('100%')
.margin({ bottom: 12 })
// 横向滚动模块卡片
Scroll() {
Row({ space: 12 }) {
ForEach(this.modules.slice(0, 5), (module: LearningModule) => {
ModuleCard({
module: module,
progress: ProgressService.getCompletionPercentage(module, this.progress),
isLocked: false,
onTap: () => {
router.pushUrl({
url: 'pages/ModuleDetail',
params: { moduleId: module.id }
});
}
})
})
}
.padding({ right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
}
.width('100%')
.padding({ left: 16, top: 20 })
.alignItems(HorizontalAlign.Start)
}
横向滚动要点
- Scroll 组件 :设置
scrollable(ScrollDirection.Horizontal) - 隐藏滚动条 :
scrollBar(BarState.Off) - 右侧留白 :Row 内部
padding({ right: 16 })
15.6 完整首页代码
Index.ets 完整实现
typescript
/**
* 首页
* 底部 5 Tab 导航 + Hero Banner + 模块轮播
*/
import { router } from '@kit.ArkUI';
import { initTheme } from '../common/ThemeUtil';
import { StorageUtil } from '../common/StorageUtil';
import { TutorialService } from '../services/TutorialService';
import { ProgressService } from '../services/ProgressService';
import { SearchService } from '../services/SearchService';
import { BadgeService } from '../services/BadgeService';
import { LearningModule, UserProgress, DEFAULT_USER_PROGRESS } from '../models/Models';
import { HeroBanner } from '../components/HeroBanner';
import { ModuleCard } from '../components/ModuleCard';
@Entry
@Component
struct Index {
@State currentTab: number = 0;
@State modules: LearningModule[] = [];
@State progress: UserProgress = DEFAULT_USER_PROGRESS;
@State isLoading: boolean = true;
@StorageLink('isDarkMode') isDarkMode: boolean = false;
aboutToAppear(): void {
this.initAndLoadData();
}
onPageShow(): void {
this.refreshProgress();
}
private async initAndLoadData(): Promise<void> {
try {
await StorageUtil.init(getContext(this));
initTheme(getContext(this));
TutorialService.init();
this.modules = TutorialService.getAllModules();
this.progress = await ProgressService.loadProgress();
SearchService.init(this.modules);
BadgeService.init(this.modules);
} catch (error) {
console.error('[Index] Failed to load data:', error);
} finally {
this.isLoading = false;
}
}
private async refreshProgress(): Promise<void> {
try {
this.progress = await ProgressService.loadProgress();
} catch (error) {
console.error('[Index] Failed to refresh progress:', error);
}
}
build() {
Column() {
if (this.isLoading) {
this.LoadingView()
} else {
Tabs({ barPosition: BarPosition.End, index: this.currentTab }) {
TabContent() {
this.HomeContent()
}
.tabBar(this.TabBuilder('首页', '🏠', 0))
TabContent() {
this.CourseContent()
}
.tabBar(this.TabBuilder('课程', '📚', 1))
TabContent() {
this.SourceCodeContent()
}
.tabBar(this.TabBuilder('源码', '📖', 2))
TabContent() {
this.ProjectContent()
}
.tabBar(this.TabBuilder('项目', '🌟', 3))
TabContent() {
this.ProfileContent()
}
.tabBar(this.TabBuilder('我的', '👤', 4))
}
.onChange((index: number) => {
this.currentTab = index;
})
.barHeight(60)
}
}
.width('100%')
.height('100%')
.backgroundColor(this.isDarkMode ? '#1a1a2e' : '#f8f9fa')
}
@Builder
LoadingView() {
Column() {
LoadingProgress()
.width(48)
.height(48)
.color(this.isDarkMode ? '#61DAFB' : '#0077b6')
Text('加载中...')
.fontSize(14)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 16 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
@Builder
TabBuilder(title: string, icon: string, index: number) {
Column() {
Text(icon)
.fontSize(24)
Text(title)
.fontSize(12)
.fontColor(this.currentTab === index
? (this.isDarkMode ? '#61DAFB' : '#0077b6')
: (this.isDarkMode ? '#d1d5db' : '#495057'))
.margin({ top: 4 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
@Builder
HomeContent() {
Scroll() {
Column() {
HeroBanner({
completedLessons: this.progress.completedLessons.length,
totalLessons: TutorialService.getTotalLessonCount(),
streak: this.progress.learningStreak,
onDailyQuestionTap: () => {
router.pushUrl({ url: 'pages/QuizPage' });
}
})
this.QuickAccessSection()
if (this.progress.currentLesson) {
this.ContinueLearningSection()
}
this.RecommendedModulesSection()
}
}
.width('100%')
.height('100%')
.scrollBar(BarState.Off)
}
// ... 其他 Builder 方法
}
本次课程小结
通过本次课程,你已经:
✅ 掌握了页面数据加载流程
✅ 实现了继续学习区域
✅ 完成了推荐模块横向滚动
✅ 实现了快捷入口功能
✅ 完成了首页的完整开发
课后练习
-
添加下拉刷新:实现下拉刷新进度数据
-
添加骨架屏:加载时显示骨架屏而非 Loading
-
添加动画效果:为模块卡片添加入场动画
下次预告
第16次:课程列表页面
我们将开发课程 Tab 的完整内容:
- 按难度分组展示
- 模块列表项设计
- 课程数量与时长显示
- 列表滚动优化
进入课程学习功能开发!