第20次:收藏功能实现
收藏功能让用户可以标记感兴趣的课程,方便后续快速访问。本次课程将完整实现收藏功能,包括服务层、状态管理和收藏页面。
项目效果





学习目标
- 掌握 BookmarkService 设计
- 学会收藏状态管理
- 实现收藏列表持久化
- 完成收藏页面开发
- 实现收藏功能全流程
20.1 收藏数据模型
Bookmark 结构
typescript
interface Bookmark {
lessonId: string; // 课程 ID
moduleId: string; // 所属模块 ID
addedAt: string; // 添加时间(ISO 格式)
}
存储键
typescript
// Constants.ets
export class StorageKeys {
static readonly BOOKMARKS: string = 'bookmarks';
}
20.2 BookmarkService 设计
服务结构
typescript
export class BookmarkService {
// 内存缓存
private static cachedBookmarks: Bookmark[] = [];
// 加载收藏
static async loadBookmarks(): Promise<Bookmark[]>;
// 添加收藏
static async addBookmark(lessonId: string, moduleId: string): Promise<void>;
// 移除收藏
static async removeBookmark(lessonId: string): Promise<void>;
// 切换收藏状态
static async toggleBookmark(lessonId: string, moduleId: string): Promise<boolean>;
// 检查是否已收藏
static isBookmarked(lessonId: string): boolean;
}
加载收藏
typescript
static async loadBookmarks(): Promise<Bookmark[]> {
try {
const bookmarks = await StorageUtil.getObject<Bookmark[]>(
StorageKeys.BOOKMARKS,
[]
);
BookmarkService.cachedBookmarks = bookmarks;
return bookmarks;
} catch (error) {
console.error('[BookmarkService] Failed to load bookmarks:', error);
return [];
}
}
保存收藏
typescript
private static async saveBookmarks(bookmarks: Bookmark[]): Promise<void> {
try {
await StorageUtil.setObject(StorageKeys.BOOKMARKS, bookmarks);
BookmarkService.cachedBookmarks = bookmarks;
} catch (error) {
console.error('[BookmarkService] Failed to save bookmarks:', error);
}
}
20.3 收藏操作实现
添加收藏
typescript
static async addBookmark(lessonId: string, moduleId: string): Promise<void> {
const bookmarks = await BookmarkService.loadBookmarks();
// 检查是否已收藏,避免重复
if (bookmarks.some(b => b.lessonId === lessonId)) {
return;
}
// 创建新收藏
const newBookmark: Bookmark = {
lessonId,
moduleId,
addedAt: new Date().toISOString()
};
bookmarks.push(newBookmark);
await BookmarkService.saveBookmarks(bookmarks);
}
移除收藏
typescript
static async removeBookmark(lessonId: string): Promise<void> {
const bookmarks = await BookmarkService.loadBookmarks();
const filtered = bookmarks.filter(b => b.lessonId !== lessonId);
await BookmarkService.saveBookmarks(filtered);
}
切换收藏状态
typescript
static async toggleBookmark(lessonId: string, moduleId: string): Promise<boolean> {
const isCurrentlyBookmarked = BookmarkService.isBookmarked(lessonId);
if (isCurrentlyBookmarked) {
await BookmarkService.removeBookmark(lessonId);
return false; // 返回新状态:未收藏
} else {
await BookmarkService.addBookmark(lessonId, moduleId);
return true; // 返回新状态:已收藏
}
}
检查收藏状态
typescript
// 同步方法,使用缓存
static isBookmarked(lessonId: string): boolean {
return BookmarkService.cachedBookmarks.some(b => b.lessonId === lessonId);
}
20.4 收藏分组功能
按模块分组
typescript
static getBookmarksByModule(): Map<string, Bookmark[]> {
const grouped = new Map<string, Bookmark[]>();
for (const bookmark of BookmarkService.cachedBookmarks) {
const moduleBookmarks = grouped.get(bookmark.moduleId) ?? [];
moduleBookmarks.push(bookmark);
grouped.set(bookmark.moduleId, moduleBookmarks);
}
return grouped;
}
获取收藏数量
typescript
static getBookmarkCount(): number {
return BookmarkService.cachedBookmarks.length;
}
清空收藏
typescript
static async clearBookmarks(): Promise<void> {
await BookmarkService.saveBookmarks([]);
}
20.5 收藏页面实现
页面结构
typescript
@Entry
@Component
struct BookmarkPage {
@State bookmarks: Bookmark[] = [];
@State isLoading: boolean = true;
@StorageLink('isDarkMode') isDarkMode: boolean = false;
aboutToAppear(): void {
this.loadBookmarks();
}
private async loadBookmarks(): Promise<void> {
this.bookmarks = await BookmarkService.loadBookmarks();
this.isLoading = false;
}
}
空状态展示
typescript
@Builder
EmptyState() {
Column() {
Text('📚')
.fontSize(64)
Text('暂无收藏')
.fontSize(18)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 16 })
Text('点击课程旁的 ☆ 添加收藏')
.fontSize(14)
.fontColor(this.isDarkMode ? '#9ca3af' : '#6c757d')
.margin({ top: 8 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
收藏列表
typescript
@Builder
BookmarkList() {
List() {
ForEach(this.bookmarks, (bookmark: Bookmark) => {
ListItem() {
this.BookmarkItem(bookmark)
}
.margin({ bottom: 8 })
}, (bookmark: Bookmark) => bookmark.lessonId)
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 12 })
.scrollBar(BarState.Off)
}
收藏项组件
typescript
@Builder
BookmarkItem(bookmark: Bookmark) {
Row() {
// 获取课程信息
Column() {
Text(this.getLessonTitle(bookmark))
.fontSize(15)
.fontWeight(FontWeight.Medium)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
Text(this.getModuleTitle(bookmark))
.fontSize(12)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 取消收藏按钮
Text('★')
.fontSize(20)
.fontColor('#fcc419')
.onClick(() => this.removeBookmark(bookmark))
// 箭头
Text('›')
.fontSize(18)
.fontColor(this.isDarkMode ? '#9ca3af' : '#6c757d')
.margin({ left: 8 })
}
.width('100%')
.padding(16)
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
.borderRadius(12)
.onClick(() => {
router.pushUrl({
url: 'pages/LessonDetail',
params: {
moduleId: bookmark.moduleId,
lessonId: bookmark.lessonId
}
});
})
}
// 获取课程标题
private getLessonTitle(bookmark: Bookmark): string {
const lesson = TutorialService.getLessonById(
bookmark.moduleId,
bookmark.lessonId
);
return lesson?.title ?? '未知课程';
}
// 获取模块标题
private getModuleTitle(bookmark: Bookmark): string {
const module = TutorialService.getModuleById(bookmark.moduleId);
return module?.title ?? '未知模块';
}
// 移除收藏
private async removeBookmark(bookmark: Bookmark): Promise<void> {
await BookmarkService.removeBookmark(bookmark.lessonId);
this.bookmarks = this.bookmarks.filter(
b => b.lessonId !== bookmark.lessonId
);
}
20.6 完整服务代码
typescript
/**
* 收藏管理服务
*/
import { StorageUtil } from '../common/StorageUtil';
import { StorageKeys } from '../common/Constants';
import { Bookmark } from '../models/Models';
export class BookmarkService {
private static cachedBookmarks: Bookmark[] = [];
static async loadBookmarks(): Promise<Bookmark[]> {
try {
const bookmarks = await StorageUtil.getObject<Bookmark[]>(
StorageKeys.BOOKMARKS,
[]
);
BookmarkService.cachedBookmarks = bookmarks;
return bookmarks;
} catch (error) {
console.error('[BookmarkService] Failed to load bookmarks:', error);
return [];
}
}
private static async saveBookmarks(bookmarks: Bookmark[]): Promise<void> {
try {
await StorageUtil.setObject(StorageKeys.BOOKMARKS, bookmarks);
BookmarkService.cachedBookmarks = bookmarks;
} catch (error) {
console.error('[BookmarkService] Failed to save bookmarks:', error);
}
}
static getCachedBookmarks(): Bookmark[] {
return BookmarkService.cachedBookmarks;
}
static async addBookmark(lessonId: string, moduleId: string): Promise<void> {
const bookmarks = await BookmarkService.loadBookmarks();
if (bookmarks.some(b => b.lessonId === lessonId)) {
return;
}
const newBookmark: Bookmark = {
lessonId,
moduleId,
addedAt: new Date().toISOString()
};
bookmarks.push(newBookmark);
await BookmarkService.saveBookmarks(bookmarks);
}
static async removeBookmark(lessonId: string): Promise<void> {
const bookmarks = await BookmarkService.loadBookmarks();
const filtered = bookmarks.filter(b => b.lessonId !== lessonId);
await BookmarkService.saveBookmarks(filtered);
}
static async toggleBookmark(lessonId: string, moduleId: string): Promise<boolean> {
const isCurrentlyBookmarked = BookmarkService.isBookmarked(lessonId);
if (isCurrentlyBookmarked) {
await BookmarkService.removeBookmark(lessonId);
return false;
} else {
await BookmarkService.addBookmark(lessonId, moduleId);
return true;
}
}
static isBookmarked(lessonId: string): boolean {
return BookmarkService.cachedBookmarks.some(b => b.lessonId === lessonId);
}
static getBookmarksByModule(): Map<string, Bookmark[]> {
const grouped = new Map<string, Bookmark[]>();
for (const bookmark of BookmarkService.cachedBookmarks) {
const moduleBookmarks = grouped.get(bookmark.moduleId) ?? [];
moduleBookmarks.push(bookmark);
grouped.set(bookmark.moduleId, moduleBookmarks);
}
return grouped;
}
static getBookmarkCount(): number {
return BookmarkService.cachedBookmarks.length;
}
static async clearBookmarks(): Promise<void> {
await BookmarkService.saveBookmarks([]);
}
}
20.7 在其他页面集成
课程详情页
typescript
// LessonDetail.ets
@State isBookmarked: boolean = false;
aboutToAppear(): void {
this.isBookmarked = BookmarkService.isBookmarked(this.lessonId);
}
// 收藏按钮
Text(this.isBookmarked ? '★' : '☆')
.fontSize(24)
.fontColor(this.isBookmarked ? '#fcc419' : '#9ca3af')
.onClick(async () => {
this.isBookmarked = await BookmarkService.toggleBookmark(
this.lessonId,
this.moduleId
);
})
模块详情页
typescript
// ModuleDetail.ets
LessonItem({
lesson: lesson,
isBookmarked: BookmarkService.isBookmarked(lesson.id),
onBookmarkTap: async () => {
await BookmarkService.toggleBookmark(lesson.id, module.id);
this.bookmarkVersion++; // 触发刷新
}
})
本次课程小结
通过本次课程,你已经:
✅ 掌握了 BookmarkService 设计
✅ 学会了收藏状态管理
✅ 实现了收藏列表持久化
✅ 完成了收藏页面开发
✅ 实现了收藏功能全流程
课后练习
-
添加排序功能:支持按添加时间排序
-
添加批量操作:支持批量删除收藏
-
添加导出功能:导出收藏列表
下次预告
第21次:测验服务层实现
我们将开发测验功能:
- QuizService 设计
- 测验数据结构
- 答案验证逻辑
- 分数计算
进入测验功能开发!