
【鸿蒙原生开发实战】第四篇多页面路由与交互——从笔记详情到搜索功能的完整实现前言上一篇我们完成了主页面和自定义组件现在「知墨」已经有了一个漂亮的笔记列表。但真正的应用需要多页面协作——用户要点进笔记看详情、编辑内容、搜索关键词。在本篇中我们将实现ArkUI 页面路由机制详解NoteDetailPage 笔记详情页NoteEditPage 新建/编辑页含表单校验、丢弃确认SearchPage 搜索页含实时搜索一、ArkUI 页面路由机制1.1 router 模块HarmonyOS 的页面路由由kit.ArkUI中的router模块提供import{router}fromkit.ArkUI;核心 APIAPI功能类比router.pushUrl({ url, params })跳转到新页面入栈Activity.startActivityrouter.back()返回上一页出栈Activity.finishrouter.replaceUrl({ url, params })替换当前页面不出栈FLAG_ACTIVITY_CLEAR_TOProuter.getParams()获取传入的参数intent.getExtras1.2 页面注册所有页面必须在module.json5的pages字段注册pages: $profile:main_pages对应的资源文件resources/base/profile/main_pages.json{src:[pages/Index,pages/NoteDetailPage,pages/NoteEditPage,pages/SearchPage,pages/SettingsPage]}注意路径相对于src/main/ets/目录不需要写.ets后缀。新增页面后必须在这里注册否则路由无效。1.3 参数传递发起跳转时传参router.pushUrl({url:pages/NoteDetailPage,params:{noteId:id}});目标页面接收参数aboutToAppear():void{constparamsrouter.getParams()asRecordstring,string;if(paramsparams[noteId]){this.noteIdparams[noteId];}}重要router.getParams()是同步的必须在aboutToAppear()中调用。不要在build()方法中调用因为 build 可能会多次执行。二、NoteDetailPage 笔记详情页2.1 页面结构详情页分为四个区域顶部导航栏返回按钮 标题 编辑/删除操作元信息区分类徽章 收藏按钮 创建/更新时间 标签内容区笔记正文空状态处理加载中 / 笔记不存在2.2 顶部操作栏Row(){Button({type:ButtonType.Circle,stateEffect:true}){Text(←).fontSize(20)}.onClick((){router.back();})Text(笔记详情).fontSize(18).fontWeight(FontWeight.Bold).layoutWeight(1).textAlign(TextAlign.Center)Button({type:ButtonType.Circle}){Text(✏️).fontSize(16)}.onClick((){this.onEdit();})Button({type:ButtonType.Circle}){Text(️).fontSize(16)}.onClick((){this.onDelete();})}2.3 元信息区设计// 分类 收藏Row({space:8}){CategoryBadge({...}).layoutWeight(1)Button({type:ButtonType.Circle}){Text(this.note!.isFavorite?⭐:☆).fontSize(18)}.onClick((){this.onToggleFavorite();})}// 标题Text(this.note!.title).fontSize(24).fontWeight(FontWeight.Bold)// 时间信息Row({space:16}){Column({space:2}){Text(创建).fontSize(11)Text(formatDate(this.note!.createdAt)).fontSize(12)}Column({space:2}){Text(更新).fontSize(11)Text(formatDate(this.note!.updatedAt)).fontSize(12)}}2.4 删除确认对话框删除操作需要防止误触我们使用AlertDialog组件asynconDelete():Promisevoid{AlertDialog.show({title:确认删除,message:确定要删除这篇笔记吗,autoCancel:true,primaryButton:{value:取消,action:(){}},secondaryButton:{value:删除,action:async(){awaitnoteUseCases.deleteNote(this.noteId);router.back();}}});}封装建议如果多个页面都需要二级确认弹窗可以抽成一个ConfirmDialog组件。不过目前只有两处使用暂时保持内联。三、NoteEditPage 新建/编辑页编辑页是应用中交互逻辑最复杂的页面因为它同时处理新建和编辑两种模式。3.1 双模式识别通过aboutToAppear()判断是否有noteId参数aboutToAppear():void{constparamsrouter.getParams()asRecordstring,string;if(paramsparams[noteId]){this.editNoteIdparams[noteId];this.isEditingtrue;this.loadNoteForEdit();}}当isEditing为 true 时标题显示为编辑笔记加载已有数据填充表单保存时调用updateNote而非addNote3.2 表单设计表单包含四个输入区域Column({space:16}){// 1. 标题输入TextInput({placeholder:笔记标题,text:this.title}).fontSize(20).fontWeight(FontWeight.Bold).placeholderColor($r(app.color.text_secondary)).onChange((v:string){this.titlev;})// 2. 分类选择器网格Column(){Text(分类).fontSize(13).fontColor($r(app.color.text_secondary))Row({space:6}){ForEach(PREDEFINED_CATEGORIES,(cat:Category){Column({space:2}){Text(cat.icon).fontSize(18)Text(cat.name).fontSize(10)}.backgroundColor(...).borderRadius(10).onClick((){this.selectedCategoryIdcat.id;})})}}// 3. 标签输入TextInput({placeholder:标签逗号分隔,text:this.tags})// 4. 内容区域TextArea({placeholder:开始记录你的想法...,text:this.content}).height(300).borderRadius(12).onChange((v:string){this.contentv;})}TextAreavsTextInputTextArea支持多行文本输入适合笔记正文TextInput是单行输入适合标题和标签。3.3 表单校验与保存asynconSave():Promisevoid{this.errorMessage;if(!this.title.trim()){this.errorMessage请输入笔记标题;return;}this.isSavingtrue;try{consttagListthis.tags.split(,).map(tt.trim()).filter(tt.length0);if(this.isEditing){constnoteawaitnoteUseCases.getNoteById(this.editNoteId);if(note){note.titlethis.title.trim();note.contentthis.content.trim();note.categoryIdthis.selectedCategoryId;note.tagstagList;awaitnoteUseCases.updateNote(note);}}else{awaitnoteUseCases.addNote(this.title,this.content,this.selectedCategoryId,tagList);}router.back();}catch(e){this.errorMessage保存失败请重试;}this.isSavingfalse;}校验逻辑标题为空 → 显示错误信息不保存标签用逗号分隔 →split(,)后逐条 trim保存按钮置灰 →enabled(!this.isSaving)防止重复提交3.4 丢弃确认如果用户编辑了内容但没保存就点击返回应该弹出确认onCancel():void{if(this.title.trim()||this.content.trim()){AlertDialog.show({title:放弃编辑,message:确定要放弃当前编辑的内容吗,primaryButton:{value:继续编辑,action:(){}},secondaryButton:{value:放弃,action:(){router.back();}}});}else{router.back();}}四、SearchPage 搜索页4.1 搜索输入框使用 ArkUI 的Search组件Row(){Button({type:ButtonType.Circle}){Text(←).fontSize(20)}.onClick((){router.back();})Search({placeholder:搜索笔记标题或内容...,value:this.query}).layoutWeight(1).height(40).backgroundColor($r(app.color.surface_variant)).borderRadius(20).onChange((v:string){this.queryv;}).onSubmit((){this.onSearch();})}SearchvsTextInputSearch组件自带搜索图标和键盘搜索按钮更适合搜索场景。4.2 搜索结果搜索页同样有三种状态if(this.isSearching){// 加载中LoadingProgress()}elseif(this.hasSearchedthis.results.length0){// 无结果EmptyState({icon:,message:未找到相关笔记,subMessage:试试其他关键词})}elseif(this.hasSearched){// 有结果Column({space:10}){Text(找到${this.results.length}篇笔记)ForEach(this.results,(item:Note){NoteCard({note:item,onTap,onToggleFavorite})})}}else{// 初次进入未搜索EmptyState({icon:,message:输入关键词搜索笔记,subMessage:支持搜索标题和正文内容})}4.3 搜索实现搜索逻辑在 NoteRepository 中asyncsearch(query:string):PromiseNote[]{constnotesawaitthis.loadAll();constqquery.toLowerCase();returnnotes.filter(nn.title.toLowerCase().includes(q)||n.content.toLowerCase().includes(q),);}当前限制这是简单的子串匹配。如果数据量大超过 1000 条建议引入倒排索引或搜索库。对于个人笔记应用当前方案足够。五、页面间完整交互流程以新建笔记→保存→查看→收藏→搜索→删除为例Index: 点击 FAB ↓ router.pushUrl({ url: pages/NoteEditPage }) NoteEditPage: 填写标题、内容、选择分类、输入标签 ↓ 点击保存 noteUseCases.addNote() ↓ router.back() Index: aboutToAppear 重新加载 notes ↓ 显示新笔记卡片 用户点击新笔记卡片 ↓ router.pushUrl({ url: pages/NoteDetailPage, params: { noteId } }) NoteDetailPage: 显示完整内容 ↓ 点击⭐收藏 noteUseCases.toggleFavorite(noteId) ↓ router.back() Index: 重新加载笔记显示收藏状态 用户点击搜索 ↓ router.pushUrl({ url: pages/SearchPage }) SearchPage: 输入关键词 ↓ noteUseCases.searchNotes(query) 搜索结果展示 NoteCard 列表 ↓ 点击搜索结果 NoteDetailPage: 查看笔记详情 ↓ 点击️删除 AlertDialog 确认 → noteUseCases.deleteNote() ↓ router.back() Index: 重新加载笔记已消失六、路由最佳实践总结场景推荐做法避免做法传参用router.getParams()在aboutToAppear中获取在 build 中获取参数返回用router.back()用pushUrl回到上一页会造成堆栈膨胀表单未保存弹出 AlertDialog 确认直接返回丢失数据删除操作先确认再执行直接删除页面注册在 main_pages.json 注册直接写路径跳转参数类型用as Recordstring, string断言直接访问未声明类型七、本阶段小结本篇我们完成了页面功能核心知识点NoteDetailPage详情查看、收藏切换、删除确认router 传参、AlertDialogNoteEditPage新建/编辑双模式、表单校验、丢弃确认条件渲染、UseCase 调用SearchPage关键词搜索、结果展示、空状态Search 组件、子串匹配搜索下一篇预告系列终篇——深色主题切换、设置页面开发、数据管理示例数据/重置、release 构建与签名发布全流程。