HarmonyOS 卡片详情到编辑闭环:router 参数、模板转实例与空白 fallback
HarmonyOS 卡片详情到编辑闭环router 参数、模板转实例与空白 fallback卡片工具最核心的链路是从模板进入详情添加到我的卡片再进入编辑页保存最后回到详情或管理页。如果这个链路里的 router 参数处理不清楚就会出现详情页空白、模板误当用户卡片、编辑页字段回填错误等问题。本章拆解CardDetailPage.ets和CardEditPage.ets的实现。详情页参数只接收 cardId 和 templateId详情页的路由参数很克制interfaceRouteParams{cardId?:string;templateId?:string;}页面不直接根据参数拼数据而是交给服务层privaterefreshData():void{constparams:RouteParams(router.getParams()??{})asRouteParams;constdetailViewappDataService.getCardDetailView(params.cardId,params.templateId);this.carddetailView.card;this.isTemplatedetailView.isTemplate;this.sceneTagsdetailView.card.sceneTags.slice();this.desktopCardIdappDataService.getDesktopCardId();}这样详情页只关心card和isTemplate。至于数据来自用户卡片、模板目录还是空白 fallback由服务层处理。模板和用户卡片共用一个详情页详情页通过isTemplate控制主按钮文案privateprimaryActionText():string{if(this.isBlankTemplate()){return新建卡片;}returnthis.isTemplate?添加到我的卡片:编辑当前卡片;}这比拆两个详情页更省维护成本。模板详情和用户卡片详情的布局一样只是动作不同。模板转实例先创建再跳编辑用户在模板详情里点击“添加到我的卡片”时项目不会直接编辑模板而是先创建一张用户卡片privatehandlePrimaryAction():void{if(this.isTemplate){if(this.card.templateId.length0){router.pushUrl({url:RoutePaths.cardEdit});return;}constcreatedappDataService.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 避免参数缺失崩溃如果详情页没有收到cardId或templateId服务层会给出空白卡片。页面用isBlankTemplate()判断privateisBlankTemplate():boolean{returnthis.isTemplatethis.card.templateId.length0;}这个 fallback 很重要。因为编辑页、新建入口、异常路由都可能出现空参数。如果页面假设router.getParams()一定有值真机上很容易崩溃。编辑页也只接收 cardId 和 templateId编辑页参数同样简单interfaceRouteParams{cardId?:string;templateId?:string;}然后通过服务层拿草稿privaterefreshData():void{constparams:RouteParams(router.getParams()??{})asRouteParams;constdraft:CardDraftModelappDataService.getCardDraft(params.cardId,params.templateId);this.draftIddraft.id;this.templateIddraft.templateId;this.titledraft.title;this.subtitledraft.subtitle;this.detaildraft.detail;this.valuedraft.value;this.footerdraft.footer;this.badgedraft.badge;this.categoryIddraft.categoryId;this.tonedraft.tone;this.favoritedraft.favorite;}编辑页没有直接读持久化数组也没有自己判断模板目录。保存时用 replaceUrl 回详情页保存卡片后编辑页调用服务层保存再替换当前路由为详情页privatesaveCard():void{constsavedappDataService.saveCardDraft(this.currentDraft());router.replaceUrl({url:RoutePaths.cardDetail,params:{cardId:saved.id}});}这里用replaceUrl的体验更好。保存后返回详情页用户点返回不会回到已经保存过的编辑页。详情页还承担桌面卡片设置用户卡片详情页可以设置为桌面卡片privatehandleDesktopAction():void{if(this.isTemplate||this.card.id.length0){return;}constupdated:booleanappDataService.setDesktopCard(this.card.id);if(!updated){return;}this.desktopCardIdthis.card.id;refreshDesktopForms();}这里明确禁止模板直接设置为桌面卡片。只有保存成用户卡片后才有稳定cardId可以同步给 Form。页面文案跟状态走详情页的摘要卡也根据状态生成privatesummaryCard():ShowcaseCardModel{return{id:this.card.id.length0?this.card.id:card-detail,title:this.card.title.length0?this.card.title:未命名卡片,subtitle:this.card.subtitle.length0?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。验证清单这条链路按下面场景测从分类模板列表进入详情按钮显示“添加到我的卡片”。点击添加后进入编辑页URL 参数里应该是cardId。保存后进入详情页详情页显示“我的卡片”状态。用户卡片详情页点击收藏状态立即刷新。用户卡片详情页点击“设为桌面卡片”桌面 Form 可刷新。直接进入无参数详情页不崩溃显示空白新建状态。小结详情到编辑闭环的核心是参数语义清楚templateId表示模板cardId表示用户卡片空参数进入 blank fallback。页面不要自己猜数据来源统一让服务层输出视图模型。这样模板市场、分类列表、首页预览、管理页编辑和桌面卡片设置都能走同一套详情编辑链路。详情页的关键不是展示而是“选中、刷新、跳转、回流”卡片详情页容易写成展示页教程但 Project028 的详情页真正承担的是卡片生命周期入口。用户从市场或分类进入详情看到模板信息后可以进入编辑页、设置为桌面卡片、打开系统桌面卡片管理器。这条链路必须把“状态写入”和“平台跳转”讲清楚。CardDetailPage.ets中的桌面卡片入口先把当前卡片写入本地状态再刷新已有桌面 Form最后打开系统 Form 管理器。顺序不能随意调换如果先打开系统管理器用户添加的可能还是旧数据如果只写本地状态不刷新已有 Form已经添加到桌面的卡片不会跟随变化。privateaddDesktopCard():void{if(!this.card){return;}appDataService.setDesktopFormCard(this.card.id);this.desktopCardIdthis.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.ets、DesktopFormService.ets、Routes.ets。