HarmonyOS旅行探索——美食详情页与参数传递的两种模式

HarmonyOS旅行探索——美食详情页与参数传递的两种模式 美食详情页是从美食指南列表点进去的——用户在 CuisinePage 里点击某道美食跳转到这个页面查看详细信息。从代码看这个页面的布局很直观顶部导航栏、渐变 Hero 区域大号 emoji、信息卡片名称 城市 描述 价格、品尝建议。但更有意思的是它的参数传递方式——和城市详情页完全不同。完整效果参数传递全字段传递 vs ID 查询城市详情页的跳转方式是传 ID// CuisinePage → CityDetailrouter.pushUrl({url:pages/CityDetail,params:{cityId:city.id}asRecordstring,Object})美食详情页的跳转方式是传全字段// CuisinePage → FoodDetailrouter.pushUrl({url:pages/FoodDetail,params:{name:f.name,emoji:f.emoji,city:f.city,desc:f.desc,price:f.price}asRecordstring,Object})两种模式的区别维度CityDetailID 查询FoodDetail全字段路由参数只传 cityId传 name/emoji/city/desc/price数据来源详情页从 CITIES 数组查找详情页直接用路由参数额外查询需要aboutToAppear 里遍历数组不需要数据一致性强始终从数据源读取弱依赖传参时的数据快照城市详情页用 ID 查询是因为城市数据量大名称、国家、描述、景点数组、美食数组全部通过路由传递不现实而且城市数据可能被其他页面引用从统一数据源读取更可靠。美食详情页用全字段传递是因为美食数据量小只有五个字段而且美食只在 CuisinePage 里出现一次不存在跨页面引用的问题。直接传字段比传 ID → 查数组少了一步查找代码更简单。参数接收Statename:stringStateemoji:stringStatecity:stringStatedesc:stringStateprice:stringaboutToAppear():void{constprouter.getParams()asRecordstring,Objectif(p){if(p[name])this.namep[name]asstringif(p[emoji])this.emojip[emoji]asstringif(p[city])this.cityp[city]asstringif(p[desc])this.descp[desc]asstringif(p[price])this.pricep[price]asstring}}五个State变量分别接收五个路由参数。每个参数都做了if (p[xxx])的存在性检查——如果调用方忘了传某个参数对应变量保持空字符串不会崩溃。和城市详情页的for循环查找不同这里只是简单的if判断 类型断言。代码量更多五行 vs 三行但逻辑更直白。Hero 区域Stack({alignContent:Alignment.Center}){Column(){}.width(100%).height(200).borderRadius(20).linearGradient({angle:135,colors:[[A,0],[#FF9F43,1]]})Text(this.emoji).fontSize(80)}.width(100%).height(200).borderRadius(20).margin({left:16,right:16,bottom:16})和城市详情页的 Hero 结构完全一样——Stack 叠加渐变背景和 emoji。区别是城市的 Hero 有 180vp 高美食的 Hero 有 200vp 高美食的 emoji 也更大80px vs 64px。美食详情页的 Hero 没有文字标题——emoji 就是全部的视觉内容。城市的 Hero 需要显示城市名、国家、描述、评分等信息所以需要更多空间。美食详情页把这些信息放在了下方的 Info 卡片里。信息卡片Column(){Text(this.name).fontSize(24).fontWeight(FontWeight.Bold).fontColor(T1).margin({bottom:8})Text( this.city).fontSize(14).fontColor(A).fontWeight(FontWeight.Medium).margin({bottom:12})Text(this.desc).fontSize(15).fontColor(T1).lineHeight(24).margin({bottom:16})Divider().strokeWidth(0.5).color(#F0EEF4).margin({bottom:14})Text(参考价格).fontSize(13).fontWeight(FontWeight.Bold).fontColor(T1).margin({bottom:4})Text(this.price).fontSize(20).fontWeight(FontWeight.Bold).fontColor(A)}信息卡片分上下两部分用Divider分隔上半部分美食名称大号黑色、城市标签橙色带定位图标、描述文字15px 行高 24px保证长文本的可读性。下半部分参考价格标签 价格数值20px 橙色粗体。价格和标签之间留了 4px 的间距比其他元素的间距小因为它们在语义上是紧密关联的。lineHeight 的作用Text(this.desc).fontSize(15).lineHeight(24)设置了 24px 的行高。如果描述文字很长需要换行24px 的行高比默认行高大约 18-20px更宽松阅读起来更舒适。在移动端 App 里正文的行高通常是字号的 1.5-1.8 倍。这里15 × 1.6 24正好是 1.6 倍是一个比较标准的正文行高。品尝建议Column(){Text(品尝建议).fontSize(16).fontWeight(FontWeight.Bold).fontColor(T1).width(100%).margin({bottom:8})Text( 推荐在当地市场或老店品尝味道最为正宗).fontSize(13).fontColor(T2).lineHeight(20).margin({bottom:8})Text( 可作为正餐或小吃根据个人口味选择配料).fontSize(13).fontColor(T2).lineHeight(20).margin({bottom:8})Text( 别忘了拍照记录分享你的美食体验).fontSize(13).fontColor(T2).lineHeight(20)}品尝建议是硬编码的通用建议——每道美食看到的都是同样的三条。这在原型阶段是合理的真正的 App 会根据每道美食的特点给出个性化建议比如寿司建议蘸酱油拉面建议先喝汤。每条建议前面加了 emoji 前缀和美食列表的城市标签风格保持一致。fontSize(13)lineHeight(20)的组合比信息卡片的描述文字更紧凑信息卡片是fontSize(15)lineHeight(24)因为建议文字是辅助信息不需要那么突出。导航栏右侧的空操作Text(❤️).fontSize(20)右上角的爱心图标目前没有绑定任何onClick事件。和运动健康 App 里的编辑按钮、“保存按钮一样这是一个 UI 占位——告诉用户这里以后可以收藏这道美食”但实际功能还没实现。如果以后要实现收藏功能需要点击时把美食信息存入UserDataService已收藏时爱心变红色fontColor(#FF4757)再次点击取消收藏但在当前版本里这个交互还没有接入。两种参数传递模式的选择标准什么时候用 ID 查询什么时候用全字段传递可以总结一个简单的判断标准用 ID 查询数据量大超过 5 个字段数据会被多个页面引用数据可能被更新需要从统一数据源读取最新值用全字段传递数据量小5 个字段以内数据只在当前场景使用数据是静态的不会被其他页面修改美食详情页完美符合全字段传递的条件——五个字段、只在 CuisinePage 里跳转、数据是写死的 mock。城市详情页符合ID 查询的条件——字段多、景点和美食数组很长、城市数据被首页和美食页共同引用。选择哪种模式不是技术偏好的问题而是数据特征决定的。