《级联年度选择》四、级联年度选择案例指南

发布时间:2026/7/17 4:49:20

《级联年度选择》四、级联年度选择案例指南 HarmonyOS 级联年度事项选择器实战指南V1/V2 混合状态管理本指南基于 HarmonyOS NEXT 开发环境使用Observed ComponentV2 混合方案从零开发一个简约风格的三列级联年度事项选择器。通过年度 → 季度 → 事项的级联选择掌握 ArkTS 中 V1/V2 状态管理混用的实战技巧与常见坑点。效果一、项目概述1.1 功能描述开发一个三列级联的年度事项选择器用户可以选择年度点击左侧年份2021~2026中间列自动切换到对应季度选择季度点击中间季度右侧列自动切换到对应事项勾选事项点击右侧事项进行选中/取消实时显示勾选标记支持全选查看已选底部显示已选数量点击可弹出已选事项面板进行管理级联全选支持全部季度和全部事项的批量操作状态自动向上同步1.2 技术架构技术点方案数据模型ObservedV1 兼容可被State持有子组件ComponentV2ParamEvent主页面ComponentStateV1 状态管理已选面板ComponentLinkStack 底部弹出级联布局三个List组件横向排列为什么不用纯 V2因为StateV1不能持有ObservedV2装饰的类实例。若页面使用 V1Component数据模型必须使用ObservedV1。子组件可继续使用ComponentV2享受其简洁的 API。1.3 项目结构entry/src/main/ets/ ├── model/ │ └── YearDataModel.ets # 数据模型定义与数据生成 ├── yearselector/ │ ├── YearSelectorPage.ets # 主页面级联逻辑 │ ├── YearColumn.ets # 年份列组件ComponentV2 │ ├── QuarterColumn.ets # 季度列组件ComponentV2 │ ├── EventColumn.ets # 事项列组件ComponentV2 │ └── SelectedTagsDialog.ets # 已选事项面板Component └── pages/ └── Index.ets # 入口页面二、数据模型设计Observed2.1 三层嵌套结构YearItem年度 └── quarters: QuarterItem[]季度列表 └── events: EventItem[]事项列表2.2 核心代码ObservedexportclassEventItem{id:number0title:stringisChecked:booleanfalseconstructor(id:number,title:string){this.ididthis.titletitlethis.isCheckedfalse}}ObservedexportclassQuarterItem{id:number0label:stringallChecked:booleanfalseevents:EventItem[][]constructor(id:number,label:string,events:EventItem[]){this.ididthis.labellabelthis.allCheckedfalsethis.eventsevents}}ObservedexportclassYearItem{id:number0year:number2024allChecked:booleanfalsequarters:QuarterItem[][]constructor(id:number,year:number,quarters:QuarterItem[]){this.ididthis.yearyearthis.allCheckedfalsethis.quartersquarters}}关键解读使用ObservedV1而非ObservedV2因为主页面使用State持有数据State与ObservedV2不兼容会导致编译错误10905348三层嵌套类都需要Observed确保ObjectLink能观测深层属性2.3 数据生成配置数据使用显式 class 定义ArkTS 禁止对象字面量作类型声明classQuarterConfig{label:stringevents:string[][]constructor(label:string,events:string[]){this.labellabelthis.eventsevents}}classYearConfig{year:number0data:QuarterConfig[][]constructor(year:number,data:QuarterConfig[]){this.yearyearthis.datadata}}exportfunctiongenerateYearData():YearItem[]{constyearConfigs:YearConfig[][newYearConfig(2026,[/* Q1~Q4 数据 */]),newYearConfig(2025,[/* Q1~Q4 数据 */]),// ... 2024 ~ 2021]returnyearConfigs.map((yc:YearConfig,yIdx:number){constquarters:QuarterItem[]yc.data.map((qd:QuarterConfig,qIdx:number){constevents:EventItem[]qd.events.map((eName:string,eIdx:number){returnnewEventItem(eIdx,eName)})returnnewQuarterItem(qIdx,qd.label,events)})returnnewYearItem(yIdx,yc.year,quarters)})}三、子组件开发ComponentV23.1 YearColumn 年份列ComponentV2exportstruct YearColumn{Paramyears:YearItem[][]ParamactiveYearIndex:number-1EventonYearSelected:(index:number)void(){}build(){List(){ForEach(this.years,(item:YearItem,index:number){ListItem(){Text(${item.year}年).fontWeight(this.activeYearIndexindex?FontWeight.Bold:FontWeight.Normal).fontColor(this.activeYearIndexindex?#3F51B5:#333333)}.backgroundColor(this.activeYearIndexindex?#EDE7F6:Color.Transparent).onClick((){this.onYearSelected(index)})},(item:YearItem)item.id.toString())}}}V2 装饰器说明装饰器作用Param接收父组件传入的数据只读子组件不直接修改Event定义回调函数通知父组件事件发生注意当前 ArkTS 编译器不支持Mutable装饰器使用会报Cannot find name Mutable错误。如需子组件修改数据通过Event回调通知父组件处理。3.2 EventColumn 事项列含勾选刷新技巧事项列是交互最复杂的一列关键在于 ForEach key 必须包含状态值ComponentV2exportstruct EventColumn{Paramevents:EventItem[][]ParamparentQuarter:QuarterItem|nullnullEventonToggleEvent:(index:number)void(){}EventonToggleAllEvents:()void(){}build(){List(){// 全选按钮ListItem(){Row(){Text(全部事项)Blank()Text(this.parentQuarter!nullthis.parentQuarter.allChecked?✓:)}}.onClick((){this.onToggleAllEvents()})// 事项列表ForEach(this.events,(item:EventItem,index:number){ListItem(){Row(){Text(item.title).layoutWeight(1)Text(item.isChecked?✓:)// 勾选标记}}.backgroundColor(item.isChecked?#E8F5E9:Color.Transparent).onClick((){this.onToggleEvent(index)})// ⚠️ 关键key 必须包含 isChecked否则状态变化不会触发重渲染},(item:EventItem)item.id.toString()_(item.isChecked?1:0))}}}为什么 key 要包含状态ComponentV2Param不会深度观测Observed对象的属性变化。当isChecked改变但id不变时ForEach 认为该项未变化不会重建 UI。将isChecked纳入 key 后状态变化 → key 变化 → 列表项重建。四、主页面级联逻辑4.1 页面结构EntryComponentstruct YearSelectorPage{Stateyears:YearItem[]generateYearData()StateactiveYearIndex:number-1StatecurrentQuarters:QuarterItem[][]StatecurrentEvents:EventItem[][]StatecurrentQuarter:QuarterItem|nullnullStateselectedTitles:string[][]StatetotalCount:number0// ⚠️ 用 State 而非 getterStateshowPanel:booleanfalsebuild(){Stack({alignContent:Alignment.Bottom}){Column(){Text(年度事项选择)Row(){YearColumn({...}).layoutWeight(2)Divider().vertical(true)QuarterColumn({...}).layoutWeight(3)Divider().vertical(true)EventColumn({...}).layoutWeight(3)}Row(){Text(已选${this.totalCount}项)Button(查看已选).onClick((){this.showPaneltrue})}}if(this.showPanel){SelectedTagsPanel({...})// 底部弹出面板}}}}为什么totalCount用State而非getArkTS struct 中的 getter 不是响应式的模板引用this.totalCount会返回undefined。必须使用State变量 手动调用refreshTotalCount()方法更新。4.2 级联选择核心逻辑级联选择的关键选择上一级时用新数组引用更新下一级数据源。// 选择年份 → 更新季度列表handleYearSelected(index:number):void{this.activeYearIndexindexthis.activeQuarterIndex-1// ⚠️ 必须用 [...array] 创建新引用否则 Param 检测不到变化this.currentQuarters[...this.years[index].quarters]this.currentEvents[]this.currentQuarternull}// 选择季度 → 更新事项列表handleQuarterSelected(index:number):void{this.activeQuarterIndexindexconstquarterthis.currentQuarters[index]// ⚠️ 同样需要新引用this.currentEvents[...quarter.events]this.currentQuarterquarter}4.3 勾选事项与刷新每次修改事项状态后必须同时刷新数组引用和计数handleToggleEvent(index:number):void{consteventthis.currentEvents[index]event.isChecked!event.isCheckedif(event.isChecked){this.selectedTitles.push(event.title)}else{this.removeByTitle(event.title)}this.updateQuarterAllCheck()this.updateYearAllCheck()// 三个刷新缺一不可this.selectedTitles[...this.selectedTitles]this.currentEvents[...this.currentEvents]// 触发事项列重渲染this.refreshTotalCount()// 更新底部计数}4.4 已选面板数据同步面板中删除标签后通过Watch同步到三层数据模型StateWatch(onRemoveTagChanged)removeTagTitle:stringonRemoveTagChanged():void{if(this.removeTagTitle)returnconsttitlethis.removeTagTitlethis.years.forEach((y:YearItem){y.quarters.forEach((q:QuarterItem){q.events.forEach((e:EventItem){if(e.titletitle){e.isCheckedfalseq.allCheckedfalsey.allCheckedfalse}})})})this.removeTagTitlethis.currentEvents[...this.currentEvents]this.currentQuarters[...this.currentQuarters]this.refreshTotalCount()}五、关键设计总结5.1 V1 与 V2 混用策略层级装饰器原因数据模型ObservedState不能持有ObservedV2类子组件ComponentV2ParamEventAPI 简洁性能好主页面ComponentStateV1 状态管理成熟兼容面板组件5.2 子组件刷新的三板斧由于ComponentV2Param不深度观测Observed对象刷新子组件需要三管齐下手段作用ForEach key 含状态值状态变化 → key 变化 → 列表项重建[...array]新引用Param检测新数组 → 子组件重渲染refreshTotalCount()手动更新State totalCount→ 计数刷新5.3 级联数据流点击年份 → [...year.quarters] → QuarterColumn 刷新 点击季度 → [...quarter.events] → EventColumn 刷新 勾选事项 → event.isChecked 变化 → [...currentEvents] 触发 ForEach 重建key 含 isChecked → refreshTotalCount() 更新计数 → updateQuarterAllCheck() updateYearAllCheck() 同步全选状态六、配置与运行6.1 页面注册在src/main/resources/base/profile/main_pages.json中添加页面路由{src:[pages/Index,yearselector/YearSelectorPage]}6.2 入口页面跳转import{router}fromkit.ArkUIButton(进入级联年度选择器).onClick((){router.pushUrl({url:yearselector/YearSelectorPage})})6.3 运行效果左侧显示 2021~2026 六个年份点击年份后中间列显示 Q1~Q4 四个季度点击季度后右侧列显示具体事项勾选事项后实时显示勾选标记底部计数同步更新点击查看已选弹出已选事项面板可逐项移除七、参考链接级联菜单选择架构指南List 组件 API状态管理 V1 Observed状态管理 V2 概述

相关新闻