一、验货列表没丢,提交按钮却没地方站仓库验货是个不太浪漫的场景。操作员拿着折叠屏设备,一边对照货架标签,一边勾选清单;展开屏幕时想同时看到项目列表和差异说明,合上设备后还得继续核对。这个页面看起来适合做成响应式两栏,但真正难处理的不是列数,而是底部那个“完成核对”按钮。假设项目叫 ShelfCheck,页面是 ReceivingReviewPage,这次固定使用十八条待核对记录,其中十二条已勾选,六条仍未确认。演示时间标注为 16:20,窗口处于 sm 断点。按钮应该保持可见,但在六条尚未完成时不能执行提交。这些都是为了说明设计而设置的样例状态,不是从设备或测试报告采集的数据。常见的直觉做法,是把内容区、补充说明和操作区并排放进一个固定高度的容器。窗口缩小后,文本先换行,列表卡片长高,最后把操作区挤到屏幕边缘。若再用固定像素把按钮往上推,字体放大或进入分屏时,问题只会换个地方出现。与其猜按钮该距离底部多少,不如先明确谁可以滚动、谁必须保留操作空间。本篇的决定很具体:验货列表允许滚动,操作区按可用窗口宽度重排,提交资格只由业务数据决定。窗口状态决定布局,不负责改变已勾选记录。这样既能解决空间挤压,也不把折叠动作误当成业务事件。## 二、先把“能不能提交”从布局里拿出来页面上的十八条记录由唯一业务编号标识,checkedIds 只保存已经确认的编号。它不保存卡片所在列号,也不保存第几个可见行。否则折叠后重新排列,序号可能变化,勾选结果就会跟错对象。先实现一个纯粹的提交资格判断。它解决的问题不是视图渲染,而是在窗口来回变化时保证业务状态稳定。这里的 CheckItem 是 Demo 自己定义的数据类型,canSubmit 也是普通 ArkTS 函数,不是 HarmonyOS 系统 API。arktsinterface CheckItem { id: string label: string}function canSubmit(rows: CheckItem[], checkedIds: string[], busy: boolean): boolean { if (busy || rows.length === 0) return false const validIds = new Set(rows.map((row: CheckItem) => row.id)) const accepted = checkedIds.filter((id: string) => validIds.has(id)) return new Set(accepted).size === rows.length}这里还多做了两个保险:只统计当前清单中存在的编号,并利用 Set 消除重复勾选。如果之后增加了搜索过滤,不能拿当前可见的六行代替全部十八行去判断是否完成。判断结果属于整张清单,而非某个设备形态下的一屏内容。busy 用来拦截重复提交。它不是让按钮变灰的装饰变量,而是业务调用的前置约束。即使页面还未及时刷新,只要状态已经进入提交中,处理函数也不应放行第二次提交。需要注意:前端门禁只能改善交互,后端仍要做幂等校验与权限判断。样例状态 12/18 的 canSubmit 结果应为 false。代码只展示此判断如何成立,没有宣称已在 DevEco Studio 编译通过或在折叠设备上实测。## 三、用 GridRow 解决横向空间,用滚动容器解决纵向空间HarmonyOS 的 ArkUI GridRow 与 GridCol 可以按断点重排列宽,onBreakpointChange 可以把当前断点回传给界面状态。官方文档中的默认宽度分界为 320vp、600vp、840vp,但本文写出对应配置,避免项目后来调整断点导致解释失效。这里参考的是窗口宽度,而不是设备型号:同一台展开设备也可能由于分屏而只有狭窄的应用窗口。第二段是页面布局的核心片段。它主要解决两栏怎样在窄窗口退化为纵向排列。为突出断点行为,省去了卡片内部标签渲染;ReceivingReviewPage 中的 currentBp 是 @State 字符串,renderChecklist 与 renderSideNotes 为项目内自行实现的 @Builder 方法,并非框架自带函数。arktsGridRow({ columns: { xs: 4, sm: 4, md: 8, lg: 12 }, gutter: { x: 12, y: 10 }, breakpoints: { value: ['320vp', '600vp', '840vp'], reference: BreakpointsReference.WindowSize }}) { GridCol({ span: { xs: 4, sm: 4, md: 5, lg: 8 } }) { this.renderChecklist() } GridCol({ span: { xs: 4, sm: 4, md: 3, lg: 4 } }) { this.renderSideNotes() }}.onBreakpointChange((bp: string) => { this.currentBp = bp})这个写法没有让说明栏在窄屏“消失”。在 xs、sm 下,两块都占满四列,内容自然顺序向下排列;到了 md,列表占五列、说明占三列;更宽时比例是八比四。拆分后的优势是信息仍完整,代价则是窄屏页面更长,因此外层要有滚动策略。布局上还要坚持一个细节:底部操作按钮不应该直接塞到无限长的列表内容末尾。更稳妥的结构是页面外层竖向容器,上方标题区,中间可伸缩的 Scroll 或 List,底部独立操作区;具体高度交由父容器的布局约束分配,不给每张卡片写死 height。如果底部存在系统安全区,需按照页面的实际安全区策略留白,不能靠截图推断固定补偿值。此外,断点改变后会发生重新排版。onBreakpointChange 的职责是记录 currentBp,不是顺手清空 checkedIds、重发订单请求或者触发提交。把这些副作用混进回调,看似少写一个函数,实际会让布局事件污染业务流水。配套 DevEco 图用的是开发界面示意画面。左侧工程目录应看到 ShelfCheck 与 ReceivingReviewPage.ets,中间突出 GridRow 的 span 和 onBreakpointChange,右侧模拟器显示十二条已核对、六条待核对的卡片状态。底部 HiLog 区域展示设计中的预期诊断文字:LAYOUT bp=sm、CHECK verified=12/18、CTA disabled=true。这些日志用于解释观察点,并不是声称已经抓取到真实日志。## 四、按钮一直能看见,也不代表一直能点不少布局问题会被误判为提交问题。实际有两种情况:一种是按钮被其他组件遮挡,用户根本点不到;另一种是按钮显示正常,却在六条未核对完成时被错误开放。前者是布局责任,后者是状态责任。修完前者不能自动假定后者也正确。第三段只讲操作逻辑。toggleOne 用业务 ID 更新勾选集合;submitReview 再次检查门禁。如果是真实产品,提交需要通过仓库服务接口完成,成功与失败都有明确反馈。此处仅留一个示例动作,不会假装模拟点击就等于服务端落库。arktsprivate toggleOne(id: string): void { if (this.committing) return const next = new Set(this.checkedIds) if (next.has(id)) next.delete(id) else next.add(id) this.checkedIds = Array.from(next)}private submitReview(): void { if (!canSubmit(this.rows, this.checkedIds, this.committing)) return this.committing = true // 实际工程:在此调用仓库接口;按结果释放 busy 并反馈 // 本示例不发送网络请求,也不伪造提交成功。}为什么要在 submitReview 里再判断一次?因为按钮是否禁用属于视图表现,业务方法可能被其他事件入口调用。将提交资格作为统一函数,可以减少未来增加快捷入口、键盘操作或自动化触发时出现的判断分叉。这段片段还有一个故意保留的边界:committing 变为 true 后,实际项目必须在异步请求的成功和失败路径中统一释放,通常放在 finally;如果界面被销毁或任务取消,还要确认请求取消与结果回写策略。为了避免把不存在的服务端接口冒充现成 API,这里不展开那部分代码。真正工程里绝不能让“提交中”永久卡住。## 五、怎么排查才不会把问题越修越大建议把调试拆成四项独立检查。先记录窗口宽度与 currentBp,看是否按预期从 md 变成 sm;再观察文本长句换行,确认操作区是否仍可见;接着检查清单的唯一编号是否保持一致;最后才看提交资格是否与 12/18 的业务状态相符。与其只截一张展开后的宽屏图,不如用同一批样例数据分别检查窄窗口、宽窗口和分屏窗口。窄窗口的预期表现是主列表与说明栏改为上下结构,卡片内容能滚动,操作区仍提供“完成核对”的位置;宽窗口的预期是分栏。无论怎样切换,都应显示同一份十二条已确认数据,而不是切换成另外的清单。如果日志显示 bp=sm,但说明栏仍占据太宽,优先检查是不是给内部卡片写了固定宽度或最小宽度;如果按钮位置正常但无法点击,优先核对 canSubmit,不要为了“让它亮起来”直接移除业务判断。两个方向需要不同的定位方法。手机效果图固定展示 ShelfCheck 的窄窗口排版:16:20、当前断点 sm、验货清单 12/18、待核对 6,操作区的“完成核对”按钮处于禁用状态。它是一幅预期界面示意图,不能替代真机、模拟器或无障碍测试。配图里用简短标注指出按钮独立操作区即可,不需要再次堆叠 IDE 代码。## 六、取舍记录与适用边界这套设计不试图用某一种机型的分辨率解决所有问题。GridRow 负责横向列宽,滚动容器负责纵向伸缩,唯一编号负责状态稳定,提交门禁负责业务正确性;各自的边界清晰,后面添加更多设备形态时不会互相拖累。但它也不是通用免测模板。极端大字号、无障碍焦点顺序、输入法弹起、动态底部导航、超长商品名称都可能引入新的约束,仍需要在目标 SDK 和实际设备上核验。列比例、断点与行间距只能依据真实窗口范围调整,不能因为文章示意图看起来合适就直接定稿。对于十八项验货任务,最终要求是:布局发生变化,业务记录不变化;操作区位置稳定,提交资格仍由十八条完整记录计算。这比“折叠时自动变两栏”更接近可维护的工程目标。参考:华为开发者文档《Responsive Grid Layout (GridRow/GridCol)》及《Adapting the App Layout to Multi-Window》;文中 API 以官方公开文档为依据,演示数据和画面为合成示例。- https://developer.huawei.com/consumer/en/doc/harmonyos-guides/arkts-layout-development-grid-layout- https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V14/multi-window-layout-adapt-V14

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐