集光 - 智能笔记应用
📱 应用简介
集光是一款专为HarmonyOS生态设计的智能笔记应用,提供安全、高效、美观的笔记管理体验。应用集成了华为账号服务,支持多种笔记编辑功能,让您的创意和想法得到完美记录。
✨ 核心特性
🔐 安全可靠
- 生物识别解锁:支持指纹、面部识别等生物认证
- 手势密码:自定义手势密码保护隐私内容
- 应用锁:为整个应用提供额外的安全保护
📝 强大编辑
- 富文本编辑:支持文字样式、颜色、大小等格式设置
- 多媒体支持:插入图片、截图,丰富笔记内容
- 智能排版:自动优化文本布局,提升阅读体验
🗂️ 智能管理
- 分类管理:创建、编辑、删除笔记分类
- 搜索功能:快速定位所需笔记内容
- 多种视图:列表视图和卡片视图自由切换
- 排序方式:按创建时间、修改时间灵活排序
🌙 个性化体验
- 夜间模式:护眼模式,适合夜间使用
- 主题切换:支持明暗主题自动切换
- 多选操作:批量管理笔记,提高效率
🏗️ 技术架构
模块化设计
集光 ├── 主应用模块 (product/phone) ├── 公共组件 (components) │ ├── 应用密码设置 (secretlock) │ ├── 富文本编辑器 (richeditor) │ ├── 用户登录 (login) │ ├── 意见反馈 (feed_back) │ └── 应用更新检测 (check_app_update) └── 公共工具 (common) ├── 数据源管理 (datasource) └── 工具类库 (utils)核心技术
- HarmonyOS 5.0+:原生HarmonyOS应用开发
- ArkTS:现代化的应用开发语言
- Stage模型:新一代应用开发模型
🚀 快速开始
环境要求
- DevEco Studio 5.0.3 Release 及以上
- HarmonyOS SDK 5.0.3 Release 及以上
- 支持设备:华为手机(包括双折叠和阔折叠)
- 系统版本:HarmonyOS 5.0.3(15) 及以上
安装配置
克隆项目
gitclone[项目地址]cdJiGaung配置华为账号服务
- 在AppGallery Connect创建应用
- 配置Client ID到
product/phone/src/main/module.json5 - 申请
quickLoginMobilePhone权限
应用签名
- 进行手工签名配置
- 添加证书公钥指纹
运行应用
- 连接调试设备
- 选择"Run > Run ‘phone’“或"Run > Debug ‘phone’”
📱 主要功能
首页功能
- 笔记展示:支持列表和卡片两种展示模式
- 分类管理:创建、编辑、删除笔记分类
- 搜索笔记:快速查找笔记内容
- 多选操作:长按多选,批量管理
- 排序功能:按时间排序,方便查找
笔记编辑
- 富文本编辑:支持文字格式、颜色、大小设置
- 图片插入:支持本地图片和截图插入
- 撤销重做:操作历史记录,支持撤销重做
- 内容分享:支持笔记内容分享到其他应用
- 自动保存:实时保存,防止内容丢失
个人中心
- 用户信息:华为账号一键登录,头像昵称管理
- 回收站:删除笔记恢复,彻底删除功能
- 隐私设置:生物识别、手势密码等安全设置
- 应用设置:夜间模式、通知设置、版本检测
- 意见反馈:问题反馈,帮助改进应用
🔧 开发指南
组件使用
如需单独使用某个组件,请参考对应组件的使用指导:
| 组件 | 描述 | 使用指导 |
|---|---|---|
| secretlock | 应用密码设置 | 使用指导 |
| richeditor | 富文本编辑器 | 使用指导 |
| login | 用户登录 | 使用指导 |
| feed_back | 意见反馈 | 使用指导 |
自定义开发
- 修改
AppScope/app.json5中的bundleName - 调整
AppScope/resources/base/element/string.json中的应用名称 - 根据需要修改主题色彩和样式
🧩 系统设计与实现说明
本节面向希望深入学习和二开的开发者,按“从需求到实现”的顺序介绍集光的整体架构、核心业务流程以及关键模块的实现思路。
1. 整体架构概览
UIAbility 入口层:
EntryAbility.ets作为应用入口,负责:- 初始化主题控制器
ThemeController,同步系统深浅色模式; - 初始化窗口尺寸,写入
AppStorage,供 UI 自适配(含折叠屏断点适配WindowUtil.registerBreakPoint); - 初始化数据库:调用
DatabaseManager.instance.initDatabase()创建和迁移数据表; - 配置状态栏、导航栏、通知授权等系统级能力。
- 初始化主题控制器
页面与导航层:
MainPage.ets作为主页面,使用Tabs + Navigation构建四个核心 Tab:PictureView:图片集NotesView:笔记集(本项目核心)ToDoView:待办集MineView:我的
- 通过
NavPathStack实现页面栈导航(如从NotesView跳转到EditNotes)。
业务页面层:
NotesView.ets:笔记列表页,负责分类筛选、多选操作、置顶、排序等;EditNotes.ets:笔记编辑页,集成富文本编辑器、背景色、分享/复制等能力;- 其它如
EditCategory.ets、SearchPage.ets等负责辅助业务。
组件与服务层:
- 公共组件在
components/下:richeditor:富文本编辑器组件与相关数据模型;secretlock:手势锁、应用锁逻辑;login:华为账号登录;feed_back:意见反馈;check_app_update:检查应用更新。
- 公共工具在
common/:datasource:数据库访问、实体与服务NoteService/CategoryService等;utils:GlobalInfoModel、AppUtil、WindowUtil等工具与全局状态。
- 公共组件在
数据存储层:
- 使用
@ohos.data.relationalStore构建notes.db; DatabaseManager.ets负责数据库初始化、迁移和默认数据插入。
- 使用
整体上可理解为:
UIAbility(入口) → MainPage(Tab 容器) → 各业务页面(Notes / EditNotes 等) → 业务服务层(NoteService / CategoryService) → DatabaseManager(RdbStore)
2. 数据模型与数据库设计
数据库通过DatabaseManager管理,使用 RDB 存储数据:
数据库配置:
- 名称:
notes.db - 安全级别:
SecurityLevel.S1
- 名称:
主要表结构:
categories表(分类):id TEXT PRIMARY KEY:分类 ID(含特殊 ID:-1= 全部笔记,-2= 未分类笔记);name TEXT:分类名称;totalCount INTEGER:该分类下笔记数量,用于快速展示计数。
notes表(笔记):id TEXT PRIMARY KEY:笔记 ID;title TEXT:标题;categoryId TEXT:所属分类 ID;content TEXT:纯文本内容;styledContent TEXT:富文本内容(通过StyleSerializer序列化的 JSON 字符串);description TEXT:用于卡片预览的摘要;createTime / updateTime TEXT:创建/更新时间;isPinned INTEGER:是否置顶;backgroundColor TEXT:笔记卡片/详情背景颜色;importance INTEGER:重要度等级;isDeleted INTEGER:是否进入回收站。
自动迁移逻辑:
DatabaseManager.migrateDatabase()使用PRAGMA table_info(notes)查询列信息;- 若缺少某个列(如
isPinned/backgroundColor/importance/styledContent/isDeleted),则通过ALTER TABLE动态新增; - 这样既支持老版本平滑升级,又避免手工 SQL 迁移出错。
默认分类初始化:
initDefaultCategories()保证:- 若不存在
id = '-1',则插入“全部笔记”; - 若不存在
id = '-2',则插入“未分类笔记”。
- 若不存在
关键实现示例(节选自
DatabaseManager.ets):
exportclassDatabaseManager{privatestatic_instance:DatabaseManager;privaterdbStore:relationalStore.RdbStore|null=null;privatereadonlySTORE_CONFIG:relationalStore.StoreConfig={name:'notes.db',securityLevel:relationalStore.SecurityLevel.S1};asyncinitDatabase():Promise<void>{constcontext=getContext(this);this.rdbStore=awaitrelationalStore.getRdbStore(context,this.STORE_CONFIG);awaitthis.createTables();awaitthis.migrateDatabase();}privateasynccreateTables():Promise<void>{if(!this.rdbStore)return;constcategoryTableSql=`CREATE TABLE IF NOT EXISTS categories ( id TEXT PRIMARY KEY, name TEXT NOT NULL, totalCount INTEGER DEFAULT 0 )`;constnoteTableSql=`CREATE TABLE IF NOT EXISTS notes ( id TEXT PRIMARY KEY, title TEXT, categoryId TEXT, content TEXT, styledContent TEXT, description TEXT, createTime TEXT, updateTime TEXT, isPinned INTEGER DEFAULT 0, backgroundColor TEXT DEFAULT "#fefefe", importance INTEGER DEFAULT 0, isDeleted INTEGER DEFAULT 0, FOREIGN KEY (categoryId) REFERENCES categories (id) )`;awaitthis.rdbStore.executeSql(categoryTableSql);awaitthis.rdbStore.executeSql(noteTableSql);awaitthis.migrateDatabase();awaitthis.initDefaultCategories();}privateasyncmigrateDatabase():Promise<void>{if(!this.rdbStore)return;constresultSet=awaitthis.rdbStore.querySql('PRAGMA table_info(notes)');lethasIsPinnedColumn=false;// 省略其它列检查...if(resultSet&&resultSet.rowCount>0){while(awaitresultSet.goToNextRow()){constcolumnName=awaitresultSet.getString(1);if(columnName==='isPinned'){hasIsPinnedColumn=true;}// 根据实际名称检查 backgroundColor / importance / styledContent / isDeleted 等}}if(!hasIsPinnedColumn){awaitthis.rdbStore.executeSql('ALTER TABLE notes ADD COLUMN isPinned INTEGER DEFAULT 0');}// 同理为其它字段做 ALTER TABLE}privateasyncinitDefaultCategories():Promise<void>{if(!this.rdbStore)return;constallNotesCountResult=awaitthis.rdbStore.querySql('SELECT COUNT(*) as count FROM categories WHERE id = ?',['-1']);// 若不存在“全部笔记”,则插入默认分类// ...}}3. 首页与笔记列表(NotesView)实现思路
3.1 MainPage:Tab 容器
- 使用
Tabs+TabContent实现四个子页面:图片集、笔记集、待办集、我的; - 自定义
tabBarBuilder绘制底部图标和文字; - 通过
curIndex控制当前选中 Tab; - 使用
GlobalInfoModel与各页面共享刷新方法,例如:- 当切换到“笔记集”Tab 时,调用
globalInfo.refreshNotesView()强制 NotesView 刷新数据; - 当切换到“图片集”Tab 时,调用
globalInfo.refreshPictureView()刷新图片数据。
- 当切换到“笔记集”Tab 时,调用
这种设计的好处是:Tab 之间无需强耦合,统一通过GlobalInfoModel进行“跨页面刷新”通信。
3.2 NotesView:笔记列表核心逻辑
NotesView.ets是笔记模块的核心列表页面,主要职责:
业务服务依赖:
NoteService:提供笔记增删改查;CategoryService:管理分类与计数;SortController:管理排序字段与顺序;NoteSearchController:管理搜索关键字与结果;SelectedController:管理多选状态;SettingController:应用设置相关(如通知授权)。
本地状态管理:
@Local noteList: Note[]:当前列表中的笔记;@Local dataList: LazyDataSource<Note>:用于懒加载/瀑布流展示的数据源;@Local categoryList: Array<Category>:分类列表;@Local showListType: SHOW_METHOD_ENUM:列表/卡片等展示方式。
生命周期逻辑(aboutToAppear):
- 重置多选状态:
selectedController.recoverInitState(); - 若开启手势锁,则跳转至
DrawLock页面; - 重置搜索关键字,获取默认分类
getFirstCategory(); - 根据当前分类、排序、搜索条件获取笔记:
noteService.getNoteList(...); - 初始化所有笔记的选中状态:
noteService.initSelectedState(); - 遍历所有分类并统计每个分类的笔记数量:
refreshAllCategoryNoteCounts; - 调用
updateDataList()同步noteList到dataList; - 将
refreshNotesView方法挂到GlobalInfoModel,供外部刷新调用。
- 重置多选状态:
刷新逻辑(refreshData):
- 重新读取当前分类下笔记并更新
noteList; - 若不在多选模式,重置选中状态;
- 调用
forceRefreshUI()清空并重建dataList,通过notifyDataChange触发 UI 刷新; - 重新统计分类计数,保证分类弹窗中的数量与实际一致。
- 重新读取当前分类下笔记并更新
多选与置顶等交互:
- 通过
SelectedController记录选中 ID 集合与计数; - 长按进入多选模式:
handleLangPress; - “全选”、“取消全选”、批量删除/移动/置顶都基于
selectedIds实现。
- 通过
关键实现示例
MainPage:Tab 切换与跨页面刷新(节选自MainPage.ets)
@Entry @ComponentV2 struct MainPage{@Local curIndex:number=0;@Provider('appPathStack')appPathStack:NavPathStack=newNavPathStack();@Local globalInfo:GlobalInfoModel=AppStorageV2.connect<GlobalInfoModel>(GlobalInfoModel,()=>newGlobalInfoModel())!;build(){Navigation(this.appPathStack){Column(){Tabs({barPosition:BarPosition.End,index:this.curIndex}){TabContent(){PictureView();}.tabBar(this.tabBarBuilder(0));TabContent(){NotesView();}.tabBar(this.tabBarBuilder(1));TabContent(){ToDoView();}.tabBar(this.tabBarBuilder(2));TabContent(){MineView();}.tabBar(this.tabBarBuilder(3));}.onChange((index:number)=>{this.curIndex=index;if(index===1&&this.globalInfo.refreshNotesView){setTimeout(()=>this.globalInfo.refreshNotesView&&this.globalInfo.refreshNotesView(),100);}if(index===0&&this.globalInfo.refreshPictureView){setTimeout(()=>this.globalInfo.refreshPictureView&&this.globalInfo.refreshPictureView(),100);}})}}}}NotesView:初始化与刷新逻辑(节选自NotesView.ets)
@ComponentV2exportstruct NotesView{noteService:NoteService=NoteService.instance;categoryService:CategoryService=CategoryService.instance;// ... 省略其它依赖@Local noteList:Note[]=[];@Local dataList:LazyDataSource<Note>=newLazyDataSource();asyncaboutToAppear():Promise<void>{this.selectedController.recoverInitState();if(this.secretLock.gesture){constparams:Record<string,Object>={'fromEntrance':true,'appPathStack':this.appPathStack};this.appPathStack.pushPathByName('DrawLock',params);}this.noteSearchController.searchKeyword='';this.currentCategory=awaitthis.categoryService.getFirstCategory();this.noteList=awaitthis.noteService.getNoteList(this.currentCategory.id,this.sortController.sortBy,this.noteSearchController.searchKeyword);this.noteService.initSelectedState();constallNotes=awaitthis.noteService.getNoteList('-1');constcategoryNotesMap=newMap<string,Note[]>();for(constcategoryofawaitthis.categoryService.getCategoryList()){if(category.id!=='-1'){constcategoryNotes=awaitthis.noteService.getNoteList(category.id);categoryNotesMap.set(category.id,categoryNotes);}}awaitthis.categoryService.refreshAllCategoryNoteCounts(allNotes,categoryNotesMap);this.categoryList=awaitthis.categoryService.getCategoryList();this.updateDataList();this.updateNoteCount();this.syncSelectedState();this.globalInfo.refreshNotesView=this.refreshData.bind(this);}asyncrefreshData():Promise<void>{this.noteList=awaitthis.noteService.getNoteList(this.currentCategory.id,this.sortController.sortBy,this.noteSearchController.searchKeyword);if(!this.selectedController.isCtrl){this.noteService.initSelectedState();}this.forceRefreshUI();this.updateNoteCount();this.syncSelectedState();}privateforceRefreshUI():void{this.dataList.clear();this.dataList.notifyDataChange(0);setTimeout(()=>{this.noteList.forEach((item:Note)=>this.dataList.pushData(item));this.dataList.notifyDataChange(0);},0);}}4. 笔记编辑(EditNotes + 富文本编辑器)实现思路
4.1 富文本编辑器组件
富文本编辑器封装在
components/richeditor中,对外通过Index.ets提供:RichEditorController:编辑器控制器,负责光标、选区、历史记录(撤销/重做)和当前样式状态;RichEditorArea:真正的富文本输入区域组件;Note/LazyDataSource<Note>等数据模型;StyleSerializer:负责将样式(加粗、斜体、下划线、阴影、对齐方式等)序列化到字符串,并在读取时反序列化回枚举和结构体。
样式持久化:
- 编辑器内部维护
MutableStyledString,记录每段文本的样式; - 保存时将
MutableStyledString通过StyleSerializer转为 JSON 字符串,存入notes.styledContent; - 读取时反序列化回
MutableStyledString,然后调用RichEditorController.restoreStateFromStyledString恢复光标位置、当前样式按钮状态等。
- 编辑器内部维护
4.2 EditNotes 页面结构
EditNotes.ets是笔记编辑的核心页面,关键成员包括:
核心依赖:
RichEditorController:单例控制器,跨页面持有编辑状态;SnapShotController:用于对当前笔记区域截图分享;NoteService:保存/更新笔记数据;ThemeController:感知深浅色模式;GlobalInfoModel:用于在保存笔记后通知NotesView刷新数据。
本地状态:
@Local currentNote: Note:当前正在编辑的笔记对象;@Local noteTitle: string:标题输入内容;@Local selectedBackgroundColor: string:当前笔记背景色;@Local lightModeColors[] / darkModeColors[]:深浅色模式下可选背景色列表;@Local isEditNote: boolean:标记是“新建”还是“编辑已有笔记”。
键盘与主题适配:
aboutToAppear中将KeyboardAvoidMode设置为RESIZE_WITH_CARET,避免软键盘遮挡输入区域;- 根据
ThemeController.currentColorMode设置默认背景色; - 启动定时器轮询主题变化,若系统从浅色切到深色,会自动切换一组更适合阅读的背景色。
工具栏与更多操作:
toolBar()中提供:撤销/重做/保存 等基础操作;moreFunctionMenu()中提供:- 分享:通过
SnapShotController.onceSnapshot()截图当前笔记区域; - 复制:通过
UnifiedData + SystemPasteboard将富文本转为纯文本复制到系统剪贴板。
- 分享:通过
保存流程(简化版):
- 用户点击工具栏中的“保存”图标;
- 调用
saveNote():- 将编辑器中的内容序列化为
content/styledContent/description; - 若为新笔记,生成
id与createTime并插入数据库; - 若为编辑已有笔记,更新数据库记录(包括
description,确保列表卡片实时更新摘要);
- 将编辑器中的内容序列化为
- 保存成功后:
- 关闭键盘与编辑状态:
controller.stopEditing(); - 将
RichEditorController.showMoreFunction设置为true,显示更多功能菜单; - 通知
NotesView刷新列表; - 结束编辑状态并返回。
- 关闭键盘与编辑状态:
关键实现示例(节选自
EditNotes.ets)
@ComponentV2 struct EditNotes{richEditorController:RichEditorController=RichEditorController.instance;noteService:NoteService=NoteService.instance;@Local currentNote:Note=newNote(newMutableStyledString(''));@Local noteTitle:string='';@Local isEditNote:boolean=false;@Local selectedBackgroundColor:string='#fefefe';asyncsaveNote():Promise<boolean>{letstyledString=this.richEditorController.controller.getStyledString();letcontent=styledString.getString();// 新建且内容和标题都为空时不保存if(content===''&&this.noteTitle===''){returnthis.isEditNote?false:true;}if(this.isEditNote){this.currentNote.updateContent(styledString,this.noteTitle,undefined,this.selectedBackgroundColor);awaitthis.noteService.updateNote(this.currentNote);}else{this.currentNote.title=this.noteTitle;this.currentNote.styledString=styledString;this.currentNote.description=styledString.getString();this.currentNote.backgroundColor=this.selectedBackgroundColor;awaitthis.noteService.addNote(this.currentNote);this.isEditNote=true;}if(this.globalInfo&&this.globalInfo.refreshNotesView){this.globalInfo.refreshNotesView();}returntrue;}build(){NavDestination(){Column(){RichEditorArea({noteTitle:this.currentNote.title,noteContent:this.currentNote.styledString,snapShotController:this.snapShotController,titleChange:(title:string)=>{this.noteTitle=title;}})}.backgroundColor(this.selectedBackgroundColor);}.menus(this.toolBar());}}5. 安全与隐私(SecretLock 等)
SecretLock组件负责应用级安全:- 手势密码解锁:在进入
NotesView时,如果检测到已设置手势密码,则通过appPathStack.pushPathByName('DrawLock', params)先进入手势解锁页面; - 解锁成功后才允许继续访问笔记内容。
- 手势密码解锁:在进入
生物识别解锁(指纹/人脸)也可以与
secretlock联动,在应用启动或从后台回到前台时进行校验。
6. 典型业务流程串联
6.1 应用启动 → 显示主页
- 系统启动
EntryAbility; onCreate中初始化主题、数据库;onWindowStageCreate中:- 注册折叠屏断点;
- 设置状态栏/导航栏属性;
windowStage.loadContent('pages/MainPage')加载MainPage;
MainPage构建 Tabs,默认显示第一个 Tab(图片集),用户可切换到“笔记集”。
6.2 进入笔记列表 NotesView
- 用户点击“笔记集”Tab,
curIndex = 1; NotesView.aboutToAppear执行:- 若开启手势锁,则跳转到手势解锁页面;
- 加载当前分类下的笔记列表;
- 同步分类计数;
- 初始化
dataList,触发 UI 渲染; - 将
refreshNotesView注册到全局,便于其它页面保存后刷新列表。
6.3 新建 / 编辑笔记
- 在
NotesView点击“新建笔记”或笔记卡片,导航到EditNotes; EditNotes.aboutToAppear:配置键盘避让、主题模式、背景色等;- 用户使用富文本工具栏编辑内容(加粗、斜体、下划线、阴影、对齐方式等);
- 点击“保存”:
- 将样式序列化为
styledContent; - 写入/更新
notes表中的记录; - 通知
NotesView刷新列表; - 结束编辑状态并返回。
- 将样式序列化为
7. 二开与扩展建议
如果你希望在本项目基础上做二次开发,可以参考以下思路:
增加字段:
- 在
notes表中新增字段(例如标签、提醒时间等); - 在
DatabaseManager.migrateDatabase()中按现有模式检测并ALTER TABLE,保持向后兼容; - 在
Note模型与NoteService中补充对应字段的读写逻辑。
- 在
扩展富文本能力:
- 在
richeditor组件中增加新的样式(例如高亮、引用块等); - 更新
StyleSerializer的序列化/反序列化逻辑; - 在
EditNotes的工具栏中增加对应的按钮和交互。
- 在
增加云同步/多端能力:
- 在
NoteService层增加与云端的同步逻辑(基于华为云或自建服务); - 建议保持本地 RDB 为“真源”,云端做备份与协同,避免弱网络导致编辑卡顿。
- 在
自定义安全策略:
- 扩展
secretlock支持更多解锁策略(如时间锁、地理位置锁等); - 在
EntryAbility.onForeground/NotesView.aboutToAppear中按需插入校验逻辑。
- 扩展
通过阅读本节并结合对应的源码文件(EntryAbility.ets、MainPage.ets、NotesView.ets、EditNotes.ets、DatabaseManager.ets、components/richeditor等),你可以较为系统地掌握集光项目的整体设计思路,并在此基础上快速完成功能扩展或二次开发。
📄 开源协议
本项目采用 Apache 2.0 开源协议,欢迎贡献代码和提出建议。
集光- 让记录更智能,让创意更闪耀 ✨