当前位置: 首页 > news >正文

鸿蒙原生应用实战(三):笔记详情与编辑页面的路由与CRUD

鸿蒙原生应用实战(三):笔记详情与编辑页面的路由与CRUD

系列目录:

  • 第一篇:项目搭建与页面架构设计
  • 第二篇:首页开发与全局数据流设计
  • 第三篇:笔记详情与编辑页面的路由与CRUD ← 当前
  • 第四篇:分类浏览与个人中心的多维数据展示
  • 第五篇:构建调试、异常处理与HAP发布

一、前言

上一篇我们完成了首页开发,实现了笔记列表展示、搜索筛选和数据流设计。本篇将开发两个核心交互页面——笔记详情页(NotePage)编辑页(EditPage),涵盖:

  • 页面间路由参数传递
  • 笔记 CRUD(增删改查)
  • 编辑态/新建态的双模式切换
  • 删除确认弹窗(bindContentCover)
  • API 23 下 router 的正确使用方法

二、鸿蒙路由机制详解

2.1 router 的正确导入

在 API 23 中,路由模块必须从@ohos.router导入:

importrouterfrom'@ohos.router';

⚠️不要@kit.AbilityKit导入——API 23 版本中该路径不导出 router。

2.2 页面跳转与传参

// 跳转并传参router.pushUrl({url:'pages/NotePage',params:{noteId:note.id}});// 无参数跳转router.pushUrl({url:'pages/EditPage'});

2.3 接收参数(含空值保护)

接收参数时使用router.getParams()必须处理 null 情况

aboutToAppear():void{// 关键:类型声明为 | null,加 if 保护letparams:Record<string,Object>|null=router.getParams()asRecord<string,Object>|null;if(params){letnoteId:number|undefined=params['noteId']asnumber|undefined;// ... 处理逻辑}}

这是最容易出错的点!如果不加| null判断,当从无参数跳转进入页面时,router.getParams()返回 null,访问params['noteId']会直接崩溃。

2.4 页面返回

router.back();// 返回上一页

关于弃用警告:在 API 23 SDK 中,pushUrlgetParamsback都会显示 deprecation warning,但功能正常可用。这些 API 要到更高版本才移除,目前无需处理。

三、笔记详情页 (NotePage)

3.1 页面结构

Column ├── 顶部导航栏 (Row) │ ├── 返回按钮 (带点击) │ ├── 标题 "笔记详情" │ └── "编辑" 文字按钮 ├── Scroll │ └── Column (内容区) │ ├── 分类标签 (带颜色边框) │ ├── 标题 (大字体粗体) │ ├── 日期 │ ├── Divider 分隔线 │ └── 正文 (lineHeight 26) └── 底部删除按钮 (Row → Button) └── bindContentCover 删除确认弹窗

3.2 加载笔记数据

通过路由参数noteId从全局 AppStorage 中查找对应笔记:

@Statenote:Note={id:0,title:'',content:'',category:'',date:''};aboutToAppear():void{letparams:Record<string,Object>=router.getParams()asRecord<string,Object>;letnoteId:number=params['noteId']asnumber;this.loadNote(noteId);}loadNote(noteId:number):void{letstored:string|undefined=AppStorage.get<string>('notes');if(stored){letallNotes:Note[]=JSON.parse(stored)asNote[];letfound:Note|undefined=allNotes.find((n:Note)=>n.id===noteId);if(found){this.note=found;}}}

这里find方法返回Note | undefined,如果数据被删除或不存在,页面会显示空内容。实际生产环境可以加上错误提示。

3.3 分类颜色标签

每个分类有不同的颜色标识:

getCategoryColor(category:string):ResourceColor{letcolorMap:Record<string,ResourceColor>={'工作':'#007AFF',// 蓝色'学习':'#34C759',// 绿色'生活':'#FF9500',// 橙色'灵感':'#AF52DE'// 紫色};returncolorMap[category]||'#999999';}

应用在 UI 上:

Text(this.note.category).fontColor(this.getCategoryColor(this.note.category)).border({width:1,color:this.getCategoryColor(this.note.category)}).borderRadius(6).alignSelf(ItemAlign.Start)

3.4 删除确认弹窗

使用bindContentCover实现底部弹出确认对话框:

@StateshowDeleteDialog:boolean=false;// 在 Column 上绑定.bindContentCover($$this.showDeleteDialog,this.DeleteDialogBuilder())// Builder 定义弹窗内容@BuilderDeleteDialogBuilder(){Column(){Text('确认删除').fontSize($r('app.float.subtitle_font_size')).fontWeight(FontWeight.Bold).margin({bottom:12})Text('确定要删除这条笔记吗?').fontColor($r('app.color.text_secondary')).margin({bottom:24})Row(){Button('取消').onClick(()=>{this.showDeleteDialog=false;})Blank().width(12)Button('确定').backgroundColor($r('app.color.delete_red')).onClick(()=>{this.showDeleteDialog=false;this.deleteNote();})}.width('100%')}.padding(24).backgroundColor($r('app.color.card_bg')).borderRadius(16).width('80%')}

⚠️ 注意$$this.showDeleteDialog的双向绑定语法——$$前缀实现状态变量和弹窗显示状态的同步。

四、编辑页面 (EditPage)

4.1 双模式设计

编辑页面同时处理新建笔记编辑已有笔记两种场景:

场景路由参数页面标题保存行为
新建无(或 noteId=0)“新建笔记”生成新 id,插入列表头部
编辑noteId=目标ID“编辑笔记”覆盖原数据
@StateisEditing:boolean=false;@StateeditNoteId:number=0;aboutToAppear():void{letparams:Record<string,Object>|null=router.getParams()asRecord<string,Object>|null;if(params){letnoteId:number|undefined=params['noteId']asnumber|undefined;if(noteId!==undefined&&noteId>0){this.isEditing=true;this.editNoteId=noteId;// 从 AppStorage 加载已有数据letstored:string|undefined=AppStorage.get<string>('notes');if(stored){letallNotes:Note[]=JSON.parse(stored)asNote[];letfound:Note|undefined=allNotes.find((n:Note)=>n.id===noteId);if(found){this.title=found.title;this.content=found.content;this.selectedCategory=found.category;}}}}}

4.2 页面结构

Column ├── 顶部导航栏 │ ├── "取消" 文字按钮 → router.back() │ ├── "新建笔记" 或 "编辑笔记" 标题 │ └── "保存" 文字按钮 → saveNote() ├── 标题输入框 (TextInput) ├── 分类选择器 (Row) │ ├── "分类" 标签 │ └── [工作] [学习] [生活] [灵感] 按钮组 ├── Divider └── 正文输入 (TextArea) ← layoutWeight(1) 撑满剩余空间

4.3 分类选择器实现

分类采用按钮组样式,单选的交互模式:

privatecategoryOptions:CategoryOption[]=[{label:'工作',value:'工作'},{label:'学习',value:'学习'},{label:'生活',value:'生活'},{label:'灵感',value:'灵感'}];Row(){Text('分类').fontColor($r('app.color.text_secondary'))Blank()ForEach(this.categoryOptions,(option:CategoryOption)=>{Text(option.label).fontColor(this.selectedCategory===option.value?Color.White:$r('app.color.text_secondary')).backgroundColor(this.selectedCategory===option.value?$r('app.color.primary'):$r('app.color.card_bg')).borderRadius(14).onClick(()=>{this.selectedCategory=option.value;})},(option:CategoryOption)=>option.value)}

4.4 保存逻辑 (CRUD)

saveNote():void{// 标题为空时不保存if(this.title.trim().length===0){return;}letstored:string|undefined=AppStorage.get<string>('notes');letallNotes:Note[]=stored?JSON.parse(stored)asNote[]:[];// 生成当前日期字符串letnow:Date=newDate();letdateStr:string=now.getFullYear()+'-'+String(now.getMonth()+1).padStart(2,'0')+'-'+String(now.getDate()).padStart(2,'0');if(this.isEditing){// UPDATE: 查找并替换letindex:number=allNotes.findIndex((n:Note)=>n.id===this.editNoteId);if(index!==-1){allNotes[index]={id:this.editNoteId,title:this.title.trim(),content:this.content.trim(),category:this.selectedCategory,date:allNotes[index].date// 保留原日期};}}else{// CREATE: 生成新ID,插入列表头部letmaxId:number=0;for(letnoteofallNotes){if(note.id>maxId){maxId=note.id;}}letnewNote:Note={id:maxId+1,title:this.title.trim(),content:this.content.trim(),category:this.selectedCategory,date:dateStr};allNotes=[newNote,...allNotes];// 新笔记在顶部}// 持久化到 AppStorageAppStorage.setOrCreate<string>('notes',JSON.stringify(allNotes));router.back();// 返回上一页}

4.5 删除逻辑

deleteNote():void{letstored:string|undefined=AppStorage.get<string>('notes');if(stored){letallNotes:Note[]=JSON.parse(stored)asNote[];// DELETE: 过滤掉目标IDallNotes=allNotes.filter((n:Note)=>n.id!==this.note.id);AppStorage.setOrCreate<string>('notes',JSON.stringify(allNotes));}router.back();// 返回上一页}

五、ArkTS 对象字面量陷阱

这是本项目遇到的一个典型编译错误:

Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)

以下写法不允许

// ❌ 编译错误:@Builder 参数类型不能是对象字面量@BuilderStatBadge(params:{label:string;value:string;color:string;}){}// ❌ 编译错误:调用 @Builder 时不能直接传对象字面量this.StatBadge({label:'工作',value:'3',color:'#007AFF'});

正确写法:

// ✅ 方案1:定义接口interfaceStatBadgeParams{label:string;value:string;color:string;}@BuilderStatBadge(params:StatBadgeParams){}// ✅ 方案2:使用独立参数@BuilderStatBadge(label:string,value:string,color:string){}this.StatBadge('工作','3','#007AFF');// 直接传值

六、本篇总结

本篇我们完成了:

  1. ✅ 鸿蒙路由机制:pushUrl传参、getParams接收(含空值保护)、back返回
  2. ✅ 笔记详情页:数据加载、分类颜色标签、删除确认弹窗
  3. ✅ 编辑页:新建/编辑双模式、分类选择器、标题+正文输入
  4. ✅ 完整 CRUD:创建(id递增+头部插入)、读取、更新、删除
  5. ✅ @Builder 参数类型的 ArkTS 严格模式避坑

下一篇将开发分类浏览页和个人中心页,展示更丰富的数据可视化内容。


http://www.jsqmd.com/news/987685/

相关文章:

  • 2026 成都爱马仕香奈儿回收指南,热门经典款行情与估价细节解析 - 奢侈品回收评测
  • 2026固化剂地坪选购全攻略:贵州厂家实力排行与避坑要点 - 品研笔录
  • 个人总结 docker搭建家庭媒体库Jellyfin
  • 华三无线认证实战:绿洲平台对接中的苹果/安卓手机优化与微信白名单配置详解
  • 石材色差是正常的吗?国家标准+验收红线全知道(2026版) - 宁波融诚石业
  • 仙人掌-圆方树学习笔记
  • HTML5语义化与无障碍实践:构建面向未来的Web基石
  • 宁波梅雨季装修石材防护专题:6-9月施工注意事项(2026版) - 宁波融诚石业
  • 别再为乱码头疼了!SOLIDWORKS工程图转DWG字体设置保姆级教程(附drawfontmap.txt修改指南)
  • 2026年粉末包装机厂家排行:全国优质品牌推荐与详细使用攻略 - 品研笔录
  • 用安信可ESP32S3开发板做个无线监控:手把手教你驱动USB摄像头并实现Wi-Fi图传
  • 警惕 “高价回收” 幌子:昆明包包回收真实利润与报价底线 - 奢侈品回收评测
  • MCP(Model Context Protocol)详细介绍
  • Word公式排版救星:MathType 7.4.8安装避坑与右编号公式实战指南
  • 图片批量翻译工具测评:功能、价格与适用场景分析
  • 广州市顺风搬家服务有限公司车辆类型有哪些?载重、尺寸、适用场景完整对比 - 生活服务
  • 别再搞混了!Windbg网络调试、远程调试与真机双机调试的实战区别与选择
  • Hermes Agent桌面版发布!Windows用户终于不用敲命令了
  • 别再乱接电阻了!手把手教你用总线耦合器搭建一个标准的1553B双冗余测试系统
  • 《代码随想录》刷题打卡day11:二叉树part01
  • 警惕“拿着 AI 找场景”:伪需求下的 Agent 泡沫
  • 洛阳商标代办哪家靠谱?选叮咚知多多,专业合规更省心 - 中媒介
  • MySQL 8.0实战:一条SQL搞定用户签到统计(INSERT ... ON DUPLICATE KEY UPDATE详解)
  • [charger][sc7061]配置
  • 宁波10个高端楼盘石材装修实景案例合集(2026版) - 宁波融诚石业
  • 告别鼠标手!Kicad 6.0 原理图与PCB设计最全快捷键清单(附PDF速查表)
  • 别再手动整理代码了!用IDEA的Save Actions插件实现保存即格式化(附避坑配置)
  • Apollo配置中心踩坑记:从IDE变量到Server.properties,优先级与缓存那些事儿
  • 高性能计算中的输出重定向:Bash与SLURM的协同工作
  • Spring AI实战:快速集成阿里通义千问