HarmonyOS旅行探索——个人中心页面与用户数据服务

HarmonyOS旅行探索——个人中心页面与用户数据服务
旅行探索 App 的我的页面和个人中心在结构上和运动健康 App 类似——用户卡片、统计数字、菜单列表。但有一个关键区别旅行 App 的个人中心引入了一个UserDataService数据服务层负责管理用户在其他页面产生的状态数据保存的城市、打卡的景点、收藏的美食。这让个人中心从纯展示升级成了数据聚合。完整效果页面结构从截图看页面从上到下依次是导航栏返回按钮 我的标题用户卡片头像 昵称 副标题已探索城市数三栏统计探索城市、打卡景点、收藏美食已保存的城市用户在城市详情页收藏的城市列表菜单列表我的行程、美食收藏、旅行工具、设置前三个区域是固定布局第四区域是动态的取决于用户收藏了哪些城市第五区域是固定入口。UserDataService用户状态的管理import{getSavedCities,getVisitedCount,getFoodSaved}from../services/UserDataService这个服务暴露了三个方法getSavedCities()返回用户收藏的城市列表getVisitedCount()返回已探索的城市数量getFoodSaved()返回收藏的美食数量这三个方法被个人中心页面调用用来填充统计数字和城市列表。从调用方式看UserDataService应该是一个全局单例——各页面通过调用它的方法来读写用户状态而不需要自己管理状态存储。这种设计的好处是数据源单一——所有页面的用户状态都从同一个服务获取不会出现不同页面数据不一致的问题。城市详情页收藏了一个城市后getSavedCities()会立即返回更新后的列表。onPageShow 的实时刷新onPageShow():void{this.refresh()}aboutToAppear():void{this.refresh()}privaterefresh():void{this.savedgetSavedCities()}refresh()在两个生命周期里被调用aboutToAppear页面首次创建时调用onPageShow每次页面从后台回到前台时调用onPageShow是关键——它保证了用户从城市详情页返回时已保存的城市列表会自动更新。如果只在aboutToAppear里加载数据用户收藏了一个城市后返回个人中心列表不会刷新因为页面没有被销毁重建。这种每次显示时刷新的模式在需要跨页面同步数据的场景里很常见。代价是每次显示都会重新调用getSavedCities()如果数据量很大或查询逻辑很复杂可能需要优化。但对于城市列表这种小数据量的场景完全没问题。用户卡片Row(){Stack(){Column(){}.width(60).height(60).borderRadius(30).linearGradient({angle:135,colors:[[A,0],[#FF9F43,1]]})Text().fontSize(28)}Column(){Text(旅行者).fontSize(18).fontWeight(FontWeight.Bold).fontColor(T1)Text(已探索 getVisitedCount() 个城市 · 旅途愉快).fontSize(11).fontColor(T3).margin({top:2})}.alignItems(HorizontalAlign.Start).margin({left:14}).layoutWeight(1)}头像区用Stack实现——底层是橙色渐变圆上层是旅行箱 emoji。和运动健康 App 的用户卡片结构完全一样只是 emoji 从跑步换成了旅行箱。副标题是动态的已探索 getVisitedCount() 个城市 · 旅途愉快。getVisitedCount()每次调用都返回最新值所以用户收藏新城市后这里的数字会自动更新。三栏统计Row({space:8}){this.Stat(getVisitedCount().toString(),探索城市)this.Stat(this.landmarkCount().toString(),打卡景点)this.Stat(getFoodSaved().toString(),收藏美食)}三个统计卡片共享同一个StatBuilder传入不同的数值和标签。三个数字都来自服务层探索城市getVisitedCount()直接返回打卡景点this.landmarkCount()需要额外计算收藏美食getFoodSaved()直接返回landmarkCount 的计算逻辑privatelandmarkCount():number{letc:number0for(leti:number0;ithis.saved.length;i){for(letj:number0;jCITIES.length;j){if(CITIES[j].idthis.saved[i].cityId){cCITIES[j].landmarks.lengthbreak}}}returnc}景点数量不是服务层直接提供的而是个人中心页面自己算的。逻辑是遍历用户保存的城市根据cityId从CITIES数组里找到对应城市累加它的landmarks.length。这种跨数据源聚合的做法在原型阶段是合理的——UserDataService管用户状态TravelData管城市数据个人中心负责把两者关联起来。如果以后数据量增大可以把这个计算移到服务层减少页面的计算负担。条件渲染已保存的城市if(this.saved.length0){Column(){Text(已保存的城市).fontSize(16).fontWeight(FontWeight.Bold).fontColor(T1).width(100%).margin({bottom:10})Column({space:8}){ForEach(this.saved,(item:SavedItem){Row(){Text(️).fontSize(20).margin({right:10})Text(item.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(T1).layoutWeight(1)SymbolGlyph($r(sys.symbol.chevron_right)).fontSize(14).fontColor([#DDD])}.width(100%).padding(12).backgroundColor(#F8F7FC).borderRadius(12).onClick((){router.pushUrl({url:pages/CityDetail,params:{cityId:item.cityId}asRecordstring,Object})})})}}}if (this.saved.length 0)是一个条件渲染——只有当用户保存了至少一个城市时才显示这个区域。如果用户没有任何收藏整个区域不渲染不会出现空白的标题栏。这个条件渲染的if在 ArkTS 里是声明式的——当this.saved.length从 0 变成 1 时整个Column会自动出现在 UI 上从 1 变回 0 时自动消失。不需要手动控制Visibility或display属性。SavedItem 的点击跳转每张保存的城市卡片点击后跳转到城市详情页传递cityId。和首页城市网格的跳转逻辑一样——传 ID详情页根据 ID 查询数据。SavedItem接口只有两个字段interfaceSavedItem{cityId:number;name:string}cityId用于跳转时传参name用于列表展示。不需要存整个City对象——详情页已经有完整的城市数据了传 ID 就够。菜单列表Column({space:0}){this.Menu(我的行程,,(){router.pushUrl({url:pages/TripPlanner})})this.Div()this.Menu(美食收藏,,(){router.pushUrl({url:pages/CuisinePage})})this.Div()this.Menu(旅行工具,,(){router.pushUrl({url:pages/ToolsPage})})this.Div()this.Menu(设置,⚙️,(){})}四个菜单项其中三个有跳转逻辑设置是空函数——目前没有实现设置页面。和运动健康 App 的个人中心一样先把入口放好功能后面再补。Menu Builder 和 Div Builder 的实现和运动健康 App 完全一样——白色背景卡片、emoji 图标 文字 右箭头、分割线从左侧 52px 开始跳过图标区域。和运动健康 App 个人中心的差异维度运动健康 App旅行探索 App数据来源全部硬编码部分来自服务层用户卡片静态运动达人动态已探索 X 个城市统计数字硬编码 156/48920/342动态计算条件渲染无菜单始终显示有无收藏时不显示城市列表跨页面同步无有onPageShow 刷新旅行 App 的个人中心比运动健康 App 多了一个关键能力——跨页面数据同步。用户在城市详情页收藏了一个城市返回个人中心后列表会自动更新。这得益于UserDataService的全局状态管理和onPageShow的刷新机制。运动健康 App 的个人中心目前没有这个能力——所有数据都是写死的不会因为用户在其他页面的操作而变化。如果以后要给运动健康 App 加上类似的功能比如累计运动天数的实时统计也需要引入一个类似的用户状态服务。数据流的闭环个人中心的数据流形成了一个闭环CityDetail收藏城市→ UserDataService存储状态 ↓ ProfilePage读取状态→ getSavedCities() / getVisitedCount() ↓ UI 更新城市列表、统计数字用户在详情页的操作收藏/取消收藏改变了服务层的状态个人中心通过onPageShow感知到页面重新显示调用refresh()读取最新状态UI 自动更新。这个闭环的关键节点是onPageShow——它是页面从后台回到前台的信号。没有这个信号个人中心就不知道什么时候该刷新数据。这也是为什么onPageShow比aboutToAppear更重要——后者只在页面首次创建时调用前者在每次显示时都调用。