HarmonyOS 卡片详情到编辑闭环:router 参数、模板转实例与空白 fallback

📅 2026/7/3 1:23:34 👁️ 阅读次数 📝 编程学习
HarmonyOS 卡片详情到编辑闭环:router 参数、模板转实例与空白 fallback

HarmonyOS 卡片详情到编辑闭环:router 参数、模板转实例与空白 fallback

卡片工具最核心的链路是:从模板进入详情,添加到我的卡片,再进入编辑页保存,最后回到详情或管理页。如果这个链路里的 router 参数处理不清楚,就会出现详情页空白、模板误当用户卡片、编辑页字段回填错误等问题。

本章拆解CardDetailPage.etsCardEditPage.ets的实现。

详情页参数只接收 cardId 和 templateId

详情页的路由参数很克制:

interfaceRouteParams{cardId?:string;templateId?:string;}

页面不直接根据参数拼数据,而是交给服务层:

privaterefreshData():void{constparams:RouteParams=(router.getParams()??{})asRouteParams;constdetailView=appDataService.getCardDetailView(params.cardId,params.templateId);this.card=detailView.card;this.isTemplate=detailView.isTemplate;this.sceneTags=detailView.card.sceneTags.slice();this.desktopCardId=appDataService.getDesktopCardId();}

这样详情页只关心cardisTemplate。至于数据来自用户卡片、模板目录还是空白 fallback,由服务层处理。

模板和用户卡片共用一个详情页

详情页通过isTemplate控制主按钮文案:

privateprimaryActionText():string{if(this.isBlankTemplate()){return'新建卡片';}returnthis.isTemplate?'添加到我的卡片':'编辑当前卡片';}

这比拆两个详情页更省维护成本。模板详情和用户卡片详情的布局一样,只是动作不同。

模板转实例:先创建,再跳编辑

用户在模板详情里点击“添加到我的卡片”时,项目不会直接编辑模板,而是先创建一张用户卡片:

privatehandlePrimaryAction():void{if(this.isTemplate){if(this.card.templateId.length===0){router.pushUrl({url:RoutePaths.cardEdit});return;}constcreated=appDataService.createCardFromTemplate(this.card.templateId);router.pushUrl({url:RoutePaths.cardEdit,params:{cardId:created.id}});return;}router.pushUrl({url:RoutePaths.cardEdit,params:{cardId:this.card.id}});}

这里的关键是:编辑页收到的是cardId,不是templateId。模板已经被复制成用户卡片,后续编辑就应该保存到用户数据。

空白 fallback 避免参数缺失崩溃

如果详情页没有收到cardIdtemplateId,服务层会给出空白卡片。页面用isBlankTemplate()判断:

privateisBlankTemplate():boolean{returnthis.isTemplate&&this.card.templateId.length===0;}

这个 fallback 很重要。因为编辑页、新建入口、异常路由都可能出现空参数。如果页面假设router.getParams()一定有值,真机上很容易崩溃。

编辑页也只接收 cardId 和 templateId

编辑页参数同样简单:

interfaceRouteParams{cardId?:string;templateId?:string;}

然后通过服务层拿草稿:

privaterefreshData():void{constparams:RouteParams=(router.getParams()??{})asRouteParams;constdraft:CardDraftModel=appDataService.getCardDraft(params.cardId,params.templateId);this.draftId=draft.id;this.templateId=draft.templateId;this.title=draft.title;this.subtitle=draft.subtitle;this.detail=draft.detail;this.value=draft.value;this.footer=draft.footer;this.badge=draft.badge;this.categoryId=draft.categoryId;this.tone=draft.tone;this.favorite=draft.favorite;}

编辑页没有直接读持久化数组,也没有自己判断模板目录。

保存时用 replaceUrl 回详情页

保存卡片后,编辑页调用服务层保存,再替换当前路由为详情页:

privatesaveCard():void{constsaved=appDataService.saveCardDraft(this.currentDraft());router.replaceUrl({url:RoutePaths.cardDetail,params:{cardId:saved.id}});}

这里用replaceUrl的体验更好。保存后返回详情页,用户点返回不会回到已经保存过的编辑页。

详情页还承担桌面卡片设置

用户卡片详情页可以设置为桌面卡片:

privatehandleDesktopAction():void{if(this.isTemplate||this.card.id.length===0){return;}constupdated:boolean=appDataService.setDesktopCard(this.card.id);if(!updated){return;}this.desktopCardId=this.card.id;refreshDesktopForms();}

这里明确禁止模板直接设置为桌面卡片。只有保存成用户卡片后,才有稳定cardId可以同步给 Form。

页面文案跟状态走

详情页的摘要卡也根据状态生成:

privatesummaryCard():ShowcaseCardModel{return{id:this.card.id.length>0?this.card.id:'card-detail',title:this.card.title.length>0?this.card.title:'未命名卡片',subtitle:this.card.subtitle.length>0?this.card.subtitle:'查看说明、场景和使用状态',value:`${this.card.usageCount}次使用`,badge:this.isBlankTemplate()?'草稿':(this.isTemplate?'模板':(this.card.favorite?'收藏':'我的')),tone:this.card.tone,imageKey:imageKeyForTemplate(this.card.templateId,this.card.categoryId)};}

这种写法能让模板、草稿、我的卡片都复用同一套 UI。

验证清单

这条链路按下面场景测:

  1. 从分类模板列表进入详情,按钮显示“添加到我的卡片”。
  2. 点击添加后进入编辑页,URL 参数里应该是cardId
  3. 保存后进入详情页,详情页显示“我的卡片”状态。
  4. 用户卡片详情页点击收藏,状态立即刷新。
  5. 用户卡片详情页点击“设为桌面卡片”,桌面 Form 可刷新。
  6. 直接进入无参数详情页,不崩溃,显示空白新建状态。

小结

详情到编辑闭环的核心是参数语义清楚:templateId表示模板,cardId表示用户卡片,空参数进入 blank fallback。页面不要自己猜数据来源,统一让服务层输出视图模型。

这样模板市场、分类列表、首页预览、管理页编辑和桌面卡片设置都能走同一套详情编辑链路。

详情页的关键不是展示,而是“选中、刷新、跳转、回流”

卡片详情页容易写成展示页教程,但 Project028 的详情页真正承担的是卡片生命周期入口。用户从市场或分类进入详情,看到模板信息后,可以进入编辑页、设置为桌面卡片、打开系统桌面卡片管理器。这条链路必须把“状态写入”和“平台跳转”讲清楚。

CardDetailPage.ets中的桌面卡片入口先把当前卡片写入本地状态,再刷新已有桌面 Form,最后打开系统 Form 管理器。顺序不能随意调换:如果先打开系统管理器,用户添加的可能还是旧数据;如果只写本地状态不刷新已有 Form,已经添加到桌面的卡片不会跟随变化。

privateaddDesktopCard():void{if(!this.card){return;}appDataService.setDesktopFormCard(this.card.id);this.desktopCardId=this.card.id;refreshDesktopForms();this.openDesktopFormManager();}

打开系统管理器时还要传入 Form 维度、Form 名称和模块名。这里不是普通页面跳转,而是 HarmonyOS Form Kit 的系统入口。DESKTOP_FORM_DIMENSION_2_4表示当前工程选择 2x4 样式作为主规格;如果后续增加 2x2 或 4x4,不应该直接改掉这个常量,而是扩展规格选择。

constwant:Want={bundleName:DESKTOP_FORM_BUNDLE_NAME,abilityName:DESKTOP_FORM_ABILITY_NAME,parameters:{'ohos.extra.param.key.form_dimension':DESKTOP_FORM_DIMENSION_2_4,'ohos.extra.param.key.form_name':DESKTOP_FORM_NAME,'ohos.extra.param.key.module_name':DESKTOP_FORM_MODULE_NAME}};formProvider.openFormManager(want);

回流视角同样关键。详情页不是最终数据源,真正的数据源是AppDataService。详情页只负责把用户意图转成服务层动作,例如收藏、编辑、设为桌面卡片。这样编辑页保存后,详情页和桌面 Form 都可以从同一个服务层重新读取,而不是互相传复杂对象。

工程检查清单

  • setDesktopFormCard()refreshDesktopForms()openFormManager()的顺序要固定。
  • 详情页不直接操作 Form UI,只表达用户动作并交给服务层。
  • 路由参数只传 id,不传完整卡片对象。
  • 异常兜底要覆盖无卡片、系统入口失败、已有桌面卡片刷新。
  • 真实路径:CardDetailPage.etsDesktopFormService.etsRoutes.ets