资讯详情

Flutter+OpenHarmony跨端实践:车辆维修状态概览界面开发全记录

📅 2026/10/18 1:44:27 | 华诺云谱 👁 阅读
Flutter+OpenHarmony跨端实践:车辆维修状态概览界面开发全记录
车主把车送进维修车间之后最渴望的一件事就是“我的车现在到底修到哪一步了”。过去想了解进度只能打电话给服务顾问顾问再回头翻工单系统来回一趟十几分钟信息还不一定准确。如果能有一块界面像快递物流一样把维修流程按节点展示出来——待接车、预检、维修中、待质检、已完成——每一步都有时间、有记录、有操作人体验感会完全不同。我们团队最近在一个售后服务数字化改造项目里落地了这样一套车辆维修状态概览界面而且采用的是Flutter 与 OpenHarmony 的跨端组合方案。一套业务逻辑同时覆盖车主手机端和门店维修终端。这篇文章我打算把整个项目从选型、架构、数据模型、界面设计、跨端适配到实际踩坑的过程完整写出来希望能给正在考虑 Flutter 跨端、或准备接 OpenHarmony 生态的开发者一些可复用的经验。如果你刚接触 Flutter或者对 OpenHarmony 这套“国产化终端适配方案”不太熟也完全可以看下去。我会把涉及底层机制的部分尽量拆开讲保证每一步为什么这么做都能看明白。1. 项目背景维修信息割裂逼我们走上跨端路线1.1 维修门店现场的真实痛点车辆维修看起来是一个“车进厂、师傅修、修完交车”的简单链条实际拆开却繁杂得多。从接车预检开始要经历派工、备件确认、维修执行、过程质检、结算清单确认、最终交车等至少六个大环节。每个环节的数据分散在不同的业务系统里工单系统管流程、配件系统管备件、质检系统管验收结果。彼此之间没有统一出口门店前台接待车主时只能来回切换系统截图或口头转述。最难受的是车主侧。车主不知道自己的车在车间里发生了什么又不好意思频繁打电话催问只能干等。服务顾问被反复询问打断车间技师也要被抽空出来汇报进度整个沟通成本非常高。我们当时接到的需求其实很朴素做一块界面把维修流程的实时状态集中展示车主要能看门店接待和技师也要能看两边看到的信息必须一致。1.2 方案选型为什么是 Flutter OpenHarmony需求方给出了两个明确条件车主侧需要支持 Android 和 iOS 手机门店侧设备要逐步替换成支持 OpenHarmony 的国产化终端。如果按老思路手机端一套原生、门店终端一套原生再加上一个后台管理界面至少要三条研发线。对于一个不到十人的客户端小组来说这种人力消耗不现实。我们当时评估了四条路线小程序、PWA、React Native、Flutter。小程序能覆盖手机端但门店的国产化终端上装不了PWA 在部分终端上内嵌浏览器的能力参差不齐React Native 在 OpenHarmony 生态里的适配成熟度当时还不够。最终落到了 Flutter 上——OpenHarmony 的适配层已经提供 Flutter 引擎能在应用内以 FlutterView 形式渲染页面这意味着 Flutter 的跨端能力可以直接延伸到 OpenHarmony 上去。所以最终架构就确定为Flutter 负责所有跨端共用的界面和业务逻辑OpenHarmony 壳工程负责系统能力和生命周期管理。说得直白一点Flutter 负责“画”和“算”OpenHarmony 负责“活”两边各干各擅长的事。1.3 维修状态概览界面需要展示什么动手写代码之前我们先把页面需要承载的内容做了明确划分。这个界面最终包含五个核心模块车辆与车主信息区车牌号、车型、颜色、车主姓名、联系电话门店人员快速确认身份维修进度概览卡片横向步骤条展示当前处于哪个大阶段细分进度时间线每个大阶段下的具体步骤记录操作时间和操作人待确认事项与提醒区备件到货、增项确认、授权补充、结算等待办快捷操作按钮联系客服、查看检测报告、支付预付款、一键导航取车车主端最关心的是“车到底到哪一步了还有多久取车”门店端最关心的是“有哪些待办需要人工介入”。一屏之内同时满足这两种信息诉求是这个界面设计的第一原则。2. 整体架构一条数据契约贯穿两端2.1 OpenHarmony 上的 Flutter 运行机制大部分 Flutter 开发者对 Android 和 iOS 的运行机制都很熟但 OpenHarmony 上 Flutter 怎么跑不少人容易混淆。这里先说一个简化版的认知模型OpenHarmony 的 Stage 模型负责应用的生命周期和系统能力比如窗口管理、权限申请、后台任务Flutter 引擎在 OpenHarmony 上被封装成一个 FlutterViewController 类似物通过 FlutterView 把 Dart 渲染界面嵌入到原生窗口中。数据流的方向是这样的OpenHarmony 原生侧接收系统事件、推送消息通过消息通道转给 Dart 层Dart 层拿到数据后统一做状态管理刷新 UI。反向也一样用户在 Flutter 界面上点击按钮Dart 层通过通道调用 OpenHarmony 原生的打电话、发通知等功能。简单一句总结Flutter 负责一切“画”和“算”OpenHarmony 负责“生命周期、权限、系统能力”。这种分工也正是我们敢用 Flutter 去做 OpenHarmony 端实现的核心前提。2.2 双端数据链路的三层结构跨端实现的重点从来不是 UI 能不能画出来而是业务数据怎么在两个壳之间稳定跑通。我们用的方案是典型的三层结构数据层云端工单服务提供数据通过 HTTP 和 WebSocket 下发给两端壳工程层OpenHarmony 工程原生接收系统事件与推送通过消息通道转给 Flutter 层业务层Flutter 统一处理展示逻辑、状态轮询、交互反馈两端之间其实不需要知道对方的存在只要遵守同一个数据契约就能渲染出几乎一致的界面。2.3 通信链路的落地方式具体实现上OpenHarmony 原生侧通过windowStage.getMainWindow()拿窗口实例然后注册数据回调把接收到的维修工单 JSON 通过 method channel 传给 Flutter 侧。Dart 侧预先注册好一个 MethodChannel监听原生侧发来的数据更新事件。// Flutter 侧接收 OpenHarmony 原生传来的维修工单数据 static const platformChannel MethodChannel(vehicle_repair/status); platformChannel.setMethodCallHandler((call) async { if (call.method onWorkOrderUpdated) { final workOrder WorkOrderModel.fromJson(call.arguments); statusStore.updateWorkOrder(workOrder); } });这里有一个关键的工程约束method channel 里传输的数据必须是平台无关的 JSON 字符串不能传自定义对象。一开始我们图省事想传二进制序列化对象调试阶段没问题真机上一跑就发现字节序在不同架构下不一致后来老老实实全部换成标准 JSON。这个坑后面还会再提。3. 维修状态的数据模型与状态机设计3.1 状态机先把流程切成可判定的节点写界面之前先得把“维修进行到哪一步”这件事定义清楚。维修流程本质上是一个状态机每个环节都有明确的前置和后置条件。我们最终把全流程切成六个大状态待接车工单创建车辆预约到店预检中技师进行车辆初步检查维修中实际维修操作中待质检维修完成等待质检复核待结算质检通过等待结算确认已完成结算完成车辆可交付每个大状态内部还会包含子步骤和操作记录。比如“维修中”内部又拆分出“等待备件、正在拆装、功能测试”等细粒度节点。3.2 数据模型设计前后端共用同一份契约以 JSON 为例每个维修工单的核心字段是这样设计的{ workOrderId: WO20240515001, vehicle: { plate: 粤B12345, model: 示例车型, color: 珠光白 }, customer: { name: 某车主, phone: 13800000000 }, status: REPAIRING, progress: 65, timeline: [ { step: CHECK_IN, label: 已接车, time: 2024-05-15 09:30, operator: 张师傅 }, { step: PRE_CHECK_DONE, label: 预检完成, time: 2024-05-15 10:00, operator: 李师傅 }, { step: PARTS_ORDERED, label: 备件已下单, time: 2024-05-15 11:20, operator: 系统自动 } ], reminders: [ { type: PARTS_ARRIVED, content: 左前大灯总成已到货, needConfirm: true } ] }这份接口是两端共用的所以从定义阶段就定死了一个规矩进度百分比的计算逻辑必须放在后端前端只负责展示后端计算好的数值不允许自己在两端各自算一遍。我们早期吃过两端进度算法不一致的亏后面专门开了一节来复盘这个坑。3.3 状态码映射表文案不写死在 UI 层状态码和展示文案的映射应当集中维护不要在 UI 层各写各的。我们维护了一套“状态码-标签-描述-颜色”四元组配置类似这样状态码标签描述颜色PENDING_RECEIVE待接车工单已创建等待车辆到店灰色PRE_CHECKING预检中技师正在预检蓝色REPAIRING维修中维修工位作业中橙色QUALITY_INSPECT待质检维修完成等待质检紫色PENDING_SETTLE待结算质检通过等待结算红色COMPLETED已完成结算完成可交车绿色这样做的最大好处是后续新增状态或修改展示文案时只改映射配置不用动任何界面代码。维修业务的状态流不是一次就定死的后续运营方很可能加“返修中”“待客户取车”之类的状态映射表驱动的方式能让扩展成本压到最低。4. 核心界面实现把“进度感”做到一眼看懂4.1 步骤进度条自绘组件不套现成控件维修状态概览界面的核心视觉元素是横向分步进度条。我们没有直接使用 Flutter 的LinearProgressIndicator因为那是一个连续进度条无法表达分步节点。最终采用自绘方式一行 Row里面交替排列圆点节点和连接线段。Widget buildStepBar(BuildContext context, WorkOrderModel order) { final steps [ StepItem(label: 待接车, status: StepStatus.done), StepItem(label: 预检中, status: StepStatus.done), StepItem(label: 维修中, status: StepStatus.active), StepItem(label: 待质检, status: StepStatus.todo), StepItem(label: 已完成, status: StepStatus.todo), ]; return Row( children: [ for (var i 0; i steps.length; i) ...[ _buildStepDot(steps[i]), if (i ! steps.length - 1) _buildStepLine(steps[i], steps[i 1]), ] ], ); }视觉设计上有几个细节值得分享已完成步骤的圆点用品牌主色填充内部画一个白色勾当前步骤的圆点放大 1.3 倍外面加一圈光晕让用户视觉焦点自动落在“现在进行到哪”未完成步骤用浅灰色圆点节点之间的连接线段用渐变已完成段主色未完成段浅灰这样用户扫一眼就能看出维修走了多远不需要读任何文字。进度条下方再配一行“预计还需 X 小时”的文案这是车主最关心的位置我们把它放在步骤条正下方。4.2 时间线组件竖向滚动替代卡片堆叠维修细项记录较多时横向展示非常局促所以我们采用竖向时间线。Flutter 里用 Column 加左侧时间轴装饰实现每一项包含图标、操作内容、时间、操作人、状态标签。时间线每项的布局[图标] 左前大灯总成更换 [时间] 2024-05-15 14:20 [人员] 张师傅 [状态] 已完成时间项之间的竖线通过在容器左侧绘制一条带渐变的直线实现。这里有个小坑直接用Container的border画竖线线会延伸到卡片外部视觉上不连贯。改成在左侧用Positioned定位一个宽度 2dp 的渐变容器让竖线只出现在两个圆点图标之间观感会自然很多。4.3 待办提醒区红色角标拉高注意力维修过程中的待确认事项是门店端的重点。我们把待办做成“红色数字角标 卡片式提醒”点击后弹出 ModalBottomSheet 展示详情。门店端和车主端的区别在于角标数据的含义门店端角标统计“需要人工处理”的待办数量车主端角标统计“需要确认授权”的事项数量。同一个组件通过入参切换数据源完全复用渲染逻辑。这也是 Flutter 跨端的价值所在——一套代码两种身份差异只体现在数据源上。4.4 响应式布局一屏兼容手机和平板这个界面要同时跑在手机上和平板类终端上布局不能写死。我们设置了两档断点宽度小于 480dp手机模式所有卡片单列堆叠宽度大于 480dp平板模式左侧为维修进度时间线右侧为车辆信息和待办提醒实现方式用的是 Flutter 的LayoutBuilder在 build 阶段拿到最大宽度后动态决定渲染哪种布局结构。比起维护两套页面这种方案省事非常多而且后续如果出现折叠屏之类的新尺寸只需要调整断点阈值。Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth 480) { return _buildTabletLayout(); } return _buildPhoneLayout(); }, ); }5. 跨端适配的隐藏战场安全区、字体、键盘与主题色5.1 安全区域与刘海屏适配OpenHarmony 设备的刘海屏、挖孔屏数据暴露方式与 Android 不完全一致。Flutter 提供了MediaQuery.removePadding统一处理安全区域但在 OpenHarmony 上要特别注意部分设备的状态栏高度是异步获取的如果页面在拿到正确安全区数据之前就完成 build内容就会被状态栏遮挡。我们的处理方式分两步OpenHarmony 壳工程侧通过windowStage.getMainWindow()获取窗口属性把安全区域值通过 method channel 传给 FlutterFlutter 侧拿到值后统一送入 MediaQuery 覆盖默认值然后再刷新 UI。// OpenHarmony 壳工程侧 window.on(windowSizeChange, (data) { this.vehicleChannel.invokeMethod(onSafeAreaChange, { top: data.safeAreaTop, bottom: data.safeAreaBottom, }); });这里的核心思想是安全区信息属于原生系统能力Flutter 不应该自己猜应该等原生侧上报后再渲染。这个时序问题不处理好即便你在 Android 上跑得再顺到了 OpenHarmony 真机上也会出现顶栏遮挡。5.2 字体与图标渲染差异跨端最容易被低估的坑就是字体渲染差异。我们遇到过一个很典型的场景部分 OpenHarmony 设备上默认字体没有覆盖到项目中使用的 iconfont 图标导致界面上的图标变成方框。即使单独加载字体文件部分低端设备上字形位置也会出现微妙偏移看起来像是图标整体左移了 1-2 像素。最终方案是把所有常用图标从 iconfont 换成 SVG 组件。SVG 是矢量渲染不依赖字库覆盖彻底根除了字体差异问题。代价是包体积增加了一点点但对于维修状态页面这种图标数量相对有限的场景完全可接受。5.3 键盘与输入法处理维修门店场景中技师需要通过平板录入车牌号这是典型的底部键盘输入需求。在 OpenHarmony 上输入法弹起时viewInsets的表现和 Android 存在差异。实测下来最强制的做法是对这类输入场景采用SingleChildScrollView包住整个页面并配置resizeToAvoidBottomInset为 true保证键盘弹起时页面能滚动到焦点输入框附近避免被遮挡。顺带说一个细节车载终端和门店平板并不都配有标准输入法。部分国产终端预装的输入法缺少数字键盘快捷切换录入车牌号这种纯数字字母混合的场景很痛苦。后来我们做了个兜底方案车牌号输入框改为自定义字母数字选择器相当于一个迷你键盘彻底绕开第三方输入法兼容问题。5.4 主题色的显式声明OpenHarmony 的部分设备会强制启用暗色模式导致应用自定义的浅色主题被系统整体覆盖界面一下子变成黑底白字原有的主色和辅助色全部错乱。解决方法是两种选一强制在壳工程配置里声明亮色主题或者监听系统主题切换事件收到变化后主动调整 Flutter theme。我们最终选择了监听方案。虽然代码量稍多但体验更完整——夜间模式下门店技师长时间使用屏幕时自动切换到暗色调对眼睛更友好而对外展示的样板上仍保留亮色主体。直接把系统主题强制锁死成亮色看上去省事实际上会伤害夜间场景的用户体验。6. 状态刷新与性能优化从白屏三秒到秒开6.1 推送与轮询结合的刷新策略维修进度不是一成不变的界面需要实时刷新。我们最终采用了 WebSocket 推送为主、轮询兜底的混合模式后端在维修状态发生切换时主动推送新状态前端收到后立即更新界面同时每 15 秒做一次轻量轮询防止推送偶发丢失导致页面停滞。需要说明的是轮询并非越多越好。轮询接口如果返回全量工单 JSON频繁触发会导致内存和电量开销都不小。我们的做法是轮询只返回状态码和进度值前端对本地已有数据进行局部更新时间线这种大字段只在状态码变化时才走全量接口。6.2 状态管理按页面维度拆分而不是全局大 Store这块踩过一个有代表性的坑。最初图省事把后端返回的大 JSON 对象整体塞进全局 Store页面直接从 Store 取值。看起来简单实际维护起来非常痛苦——任何一处细节改动都要走完整的异步更新链路而各个子组件因为订阅了同一份状态一改全改重建开销巨大。调整后的策略是状态管理按页面维度拆分不用一个全局 Store 装下所有内容数据做扁平化处理把 workOrder 里的 timeline、reminders 等字段拆分出去组件刷新时用 select 只订阅自己关心的字段其他字段变化不触发无关组件重建具体到 Flutter我们选择的是轻量级状态管理方案配合组件级别的监听器。这样维修状态页里只有真正依赖progress字段的进度条组件会重建时间线组件不会因为 vehicle 字段变化而跟着刷新。6.3 低配置设备上的性能优化维修门店的国产化终端配置普遍不高。如果没有针对性地优化时间线列表在真机上的滚动帧率会明显掉。我们做了三件实质优化时间线列表改用ListView.builder懒加载不用 Column 全量渲染每个时间项包一层RepaintBoundary避免列表滚动时整个页面重复绘制车辆图片统一走服务端裁剪缩略图按屏幕宽度生成禁止原图直出除此之外首屏渲染路径也做了精简。非核心卡片全部懒加载维修进度卡片优先展示本地缓存的上一次数据等网络请求返回新数据再替换。这样首屏可交互时间从最初的 3200ms 优化到 1100ms 左右在低端平板上的体验差异非常明显。7. 踩坑实录崩溃、进度不一致与低端机适配7.1 OpenHarmony 真机闪退窗口时序问题先分享一个排查时间最长的崩溃问题。当时在 OpenHarmony 真机上页面来回切换时会出现偶发闪退模拟器上完全无法复现。排查思路一开始放在 Dart 侧怀疑是状态清理不干净怎么查都查不到问题。后来把定位点转到 OpenHarmony 壳工程发现根因是窗口生命周期时序页面切走时OpenHarmony 原生侧已经释放了窗口句柄但 Flutter 侧还在接收动画指令和持续绘制。两边步调不一致崩溃就发生了。解决方案是在 OpenHarmony 壳工程侧监听 onPageHide 事件通过 method channel 通知 Flutter 暂停动画、暂停轮询和异步任务页面重新可见时再恢复。本质上就是给 Flutter 页面增加统一的“后台暂停”协议避免切后台之后还疯狂刷新界面。7.2 两端进度条不一致的根因项目早期手机端和 OpenHarmony 端各自写着“根据状态计算进度”的逻辑结果同一份工单两端显示的进度百分比差了 5%。问题定位到根因后我们把进度计算逻辑整体挪到了后端后端根据状态流转时间戳和预设工序权重统计算好百分比前端只负责展示。看起来是移了一个小函数实际上是消灭整整一类“两端解释不一致”的问题。这个经验非常通用凡是涉及状态推导、数值计算、文案映射的规则都应该由服务端统一输出前端不自己算。前端一旦有“自主发挥”的空间跨端一致性就会变成无底洞。7.3 低配置机器上的白屏与内存问题首屏白屏时间过长是项目上线初期被吐槽最多的点。除了前面提到的懒加载和缓存还有一个细节容易被忽略Flutter 在低端设备上运行初次构建 UI 时会创建大量对象如果首屏一次性把所有模块全部渲染出来内存峰值很容易冲高。我们的最终策略是“渐进式页面填充”先渲染车辆信息区和进度条这两个模块业务价值最高且渲染开销最小时间线和待办提醒区等线程空闲时再渲染。配合本地缓存先出框架整体感知速度提升非常明显。7.4 跨端通用性带来的取舍最后想聊一个关于取舍的体会。Flutter 跨端能力确实强但这不代表所有代码都能无脑复用。项目过程中我们把代码划分成了三层完全共用层数据模型、状态映射、纯逻辑工具、条件共用层布局结构共用细节样式按宽度和平台微调、完全平台层推送接收、安全区获取、系统主题监听。这种分层方式既保证了跨端复用的收益又避免过度抽象。如果从一开始就追求“一段代码跑遍所有端”结果往往是每端都跑不顺最后反而要在平台层打补丁。整个项目落地的最大收获其实不在于界面最终长什么样。真正有价值的是“跨端”这个概念被重新定义了一遍它不只是让代码在两套系统上跑起来而是逼着你把数据契约、状态机、平台差异、性能边界全部重新梳理清楚。如果你正在考虑 Flutter 加 OpenHarmony 的跨端方案希望这份记录能帮你避开我们踩过的那些坑。最后再分享一个小建议遇到跨端问题时先别急着在 Flutter 侧打补丁优先确认问题到底是 Dart 渲染层出的还是壳工程生命周期导致的。把问题准确地归到正确的层排查效率能高出一倍。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑