
1. 项目背景与选型思路1.1 为什么在 OpenHarmony 上做 Flutter 对话框做 OpenHarmony 北向应用的开发者多少都经历过选型的纠结。纯 ArkUI 声明式开发确实足够原生但问题是团队里如果已经有一套 Flutter 代码库或者团队本身更熟悉 Dart 生态那面对 OpenHarmony 的设备尤其是 RK3568 这类开发板时最务实的方案其实是复用 Flutter 的跨端能力把业务层尽可能多地沉淀下来。提示对话框这个需求看着不起眼却是任何 App 都绕不开的基础交互组件。登录确认、删除二次确认、版本更新提示、权限申请说明全是靠它撑起来的。正因为它足够基础反而最适合作为 Flutter for OpenHarmony 的首个实战切入点——既能验证 Flutter 在鸿蒙设备上的渲染链路是否顺畅又能把工程化配置、依赖管理、原生插件桥接这一套流程走通。把这一关过了后面的页面跳转、数据展示、状态管理全部可以直接照搬这套基建。1.2 基于标题热词的技术环境确认写这篇文章前我顺手翻了下相关热词有几个值得注意的信号大量开发者在问 Flutter 安装与配置、Flutter 版本不对导致依赖包下不下来、Flutter 热重载后浏览器没更新以及 OpenHarmony 的 RK3568 设备树到底怎么选。能看得出目前真正卡住大家的往往不是业务代码怎么写而是环境搭建和工具链适配。选择 RK3568 作为目标设备是社区里最常见的做法。这块板子性能虽然不算强但好在 OpenHarmony 的适配比较完善跑 Flutter 的 Dart VM 和 Skia 渲染管线性能勉强够用。设备树选择的问题其实不复杂OpenHarmony 官方发行的版本通常会内置多套 dts设备树文件对应不同厂商的 RK3568 板卡你只需要在编译或烧录前确认自己板子的型号是 EVB1、EVB2 还是第三方定制的再通过 boot 镜像里的 u-boot 参数或 config 文件指定对应的 dts 即可多数情况下默认配置就能正常开机。真正容易翻车的反而是 Flutter SDK 与 OpenHarmony SDK 的版本对齐问题这个我会在后面环境章节仔细讲。2. 环境准备与工程搭建2.1 开发环境完整配置清单在开始写任何一行 Dart 代码之前先把环境踩平。这里以 OpenHarmony 4.0 Release 版本为例因为我实测这套组合最稳。必备的组件包括DevEco Studio 4.0北向集成开发环境用于编译 OpenHarmony 的 HAP 包Flutter SDK建议 3.7.x 以上因为 OpenHarmony 的 Flutter 适配分支基于这个版本起才开始稳定OpenHarmony Flutter 适配仓库社区维护的 flutter_flutter 分支或者通过 gitee 上搜索 OpenHarmony SIG 的 flutter 适配工程安装过程本身不复杂但有一个关键点必须提醒不要用 Flutter 官方主分支也就是 https://github.com/flutter/flutter 这个仓库直接用官方 SDK 编译 OpenHarmony 目标时是拿不到 ohos 这个 target platform 的。你需要先配置 Flutter 的 custom fork也就是 OpenHarmony 社区维护的那条分支然后在该分支下执行 flutter doctor才会看到 ohos 设备的支持选项。具体配置方式在 Flutter 根目录下通过 git remote -v 确认当前 remote 是否指向 OpenHarmony 仓库然后执行git remote set-url origin https://gitee.com/openharmony-sig/flutter_flutter.git git fetch origin git checkout openharmony-4.0-release之后在 pubspec.yaml 里同样要把 sdk 相关的依赖源切到社区镜像否则拉取 flutter sdk 自带的包时容易 404。2.2 创建 Flutter 工程并打通鸿蒙编译链路环境就绪后用命令行的方式创建工程尽量别用 IDE 的图形化向导因为 Flutter 创建的默认目录结构在 OpenHarmony 工程里需要做一点调整命令行控制起来更明确flutter create --org com.example --project-name dialog_demo ohos_dialog_demo cd ohos_dialog_demo接着需要在工程目录下手动新增一个ohos目录这是 OpenHarmony 的工程壳。目前 Flutter 官方模板不会自动生成这个目录需要从社区示例工程里拷贝ohos文件夹或者通过 DevEco Studio 对 Flutter 工程执行 New Module 来生成 HarmonyOS 模块。这里有个很容易搞错的点OpenHarmony 的 UIAbility 入口文件里必须显式配置 Flutter 的容器视图。打开ohos/entry/src/main/ets/entryability/EntryAbility.kt核心代码是import ohos.stage.ability.adapter.StageAbility import ohos.stage.ability.adapter.StageAbilityContext import ohos.stage.ability.adapter.IFlutterAbility class EntryAbility : StageAbility(), IFlutterAbility { override fun getFlutterRoute(): String { return main } }同时module.json5里的 abilities 节点下需要把 EntryAbility 注册为 stage 模型并配置srcEntry: entry/src/main/ets/entryability/EntryAbility.kt。这些做完后回到工程根目录执行flutter build hap --debug如果没报错说明 Flutter 到 OpenHarmony 的编译链路已经打通。首次构建会花很长时间因为要下载 OpenHarmony 的 native 依赖得有点耐心。3. 提示对话框核心实现3.1 用 showDialog 和 AlertDialog 完成基础弹窗接下来是正文了。Flutter 的对话框体系核心就两个 APIshowDialog负责把一个 Dialog 组件压入路由栈AlertDialog负责定义对话框的视觉与结构。在 OpenHarmony 上这两个 API 的行为与 Android 上基本一致因为 Flutter 框架层的代码是跨平台通用的真正不一样的地方在下层的 Platform Channel 实现。我先给出一个最精简可用的代码Futurevoid _showSimpleDialog(BuildContext context) async { await showDialogvoid( context: context, barrierDismissible: true, builder: (BuildContext context) { return AlertDialog( title: const Text(确认操作), content: const Text(该操作会清理本地缓存是否继续), actions: [ TextButton( onPressed: () Navigator.of(context).pop(), child: const Text(取消), ), TextButton( onPressed: () { // 执行实际业务逻辑 Navigator.of(context).pop(true); }, child: const Text(确定), ), ], ); }, ); }这里有三个细节值得展开说。barrierDismissible控制点击遮罩层时是否关闭弹窗。对破坏性操作删除、覆盖、退出建议改成 false同时配上PopScope拦截返回键防止用户误触直接关掉。这个习惯在 RK3568 这类带物理按键的开发板上尤其重要用户很可能随手按返回键。Navigator.of(context).pop()的返回值是动态类型的可以传任意对象。也就是说_showSimpleDialog可以返回一个用户的选择结果这个特性为后面做页面级状态联动打好了基础。actions数组里的按钮顺序在 Material 规范下是有视觉层级的确认按钮放右侧、取消按钮放左侧但如果你做的是国际化 App需要注意 RTL从右到左地区的习惯阿拉伯语和希伯来语下按钮顺序会镜像翻转。Flutter 的 Directionality 控件会自动处理前提是你所有的文字都用了Text而不是硬编码TextDirection。3.2 定制对话框主题色与样式Flutter 生态里showLicensePage这种系统级页面是可以全局改主题色的但AlertDialog的定制需要单独做。整体有两种改法一种是全局统一改的是ThemeData.dialogTheme另一种是局部单个对话框定制。全局改法是在MaterialApp的theme里增加配置MaterialApp( theme: ThemeData( dialogTheme: DialogThemeData( backgroundColor: const Color(0xFFF5F5F5), titleTextStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF333333), ), contentTextStyle: const TextStyle( fontSize: 14, color: Color(0xFF666666), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), home: const HomePage(), )这种做法适合绝大多数业务场景特别是当你的 App 有多套皮肤比如暗色模式时只需要额外配置darkTheme下的dialogTheme对话框就会跟随系统自动切换。局部定制更灵活但容易写出代码异味。我的建议是超过三个地方用到相同样式的对话框时就必须抽成公共组件不要每次都写一遍RoundedRectangleBorder。比如包装一个AppConfirmDialog内部封装标题、内容、按钮文案与回调类型业务层只需要传入数据不需要关心视觉效果。这里要专门提醒一下字体问题。OpenHarmony 默认字体是 HarmonyOS Sans但 Flutter 渲染时并不会自动读取系统字体需要你在pubspec.yaml中手动引入字体文件或者通过ThemeData.fontFamily指定。否则中文文字在某些设备上会回退到系统自带兜底字体显示效果不稳定可能出现数字与汉字宽度不协调的情况。我在 RK3568 上实测回退字体的粗体显示存在明显锯齿把字体文件导入后清晰度立刻上来了。3.3 对话框中的输入与表单验证实际项目里对话框不止是确认和取消很多时候还要承担输入任务比如输入密码确认身份填写服务器地址重命名文件。这种情况下AlertDialog内部可以直接塞一个TextField但要注意 IME软键盘的避让问题。在 Flutter 里TextField会自动处理键盘弹出但会在对话框被顶起时出现布局溢出。常见的解决方案是确保AlertDialog放在SingleChildScrollView里并配合contentPadding调整间距。AlertDialog有一个scrollable属性可以直接设为trueFlutter 会自动在 content 区域启用滚动这样键盘弹出就不会挤压按钮区域。输入场景下的表单校验我建议用FormTextFormField而不是手动监听TextEditingController理由是两个Form自带的validate()方法可以把所有校验一次性触发用户体验更连贯错误提示文案的颜色和动画由框架统一管理不需要自己维护状态。AlertDialog( title: const Text(请输入服务器地址), content: Form( key: _formKey, child: TextFormField( controller: _controller, validator: (value) { if (value null || value.isEmpty) { return 地址不能为空; } return null; }, ), ), actions: [ TextButton( onPressed: () { if (_formKey.currentState!.validate()) { Navigator.of(context).pop(); } }, child: const Text(提交), ), ], )需要注意在showDialog的 builder 里创建Form时GlobalKey的作用域必须谨慎。如果这个 key 在对话框外部也声明了而对话框还没弹出时_formKey.currentState就会是 null。我习惯把校验逻辑封装在对话框组件内部由组件自己持有 key外部只接收结果这样职责清晰也不容易踩空指针。4. 状态交互与异步回调4.1 如何把对话框结果传递到业务层对话框最核心的价值不是展示而是用户点了什么。所以把用户的决策准确地回传给业务层是衡量一个对话框组件是否好用的分水岭。先看最基础的场景简单确认框。通过Navigator.pop(value)传回 true/false调用方用 await 接收返回值即可final bool? result await _showSimpleDialog(context); if (result true) { // 用户点了确定 }看上去很简单但实际项目里会有两个坑。第一showDialog返回的类型是FutureT?当用户点击返回键或者点击遮罩层关闭时返回值是 null而不是 false。如果你的代码写成if (result false)那么 null 分支就会被忽略逻辑就漏处理了。正确写法是明确判断result true或使用空安全??提供默认值。第二在对话框还未关闭前如果页面已经被销毁比如用户快速连续按返回键退出了页面await之后的回调仍然会执行。此时如果你在回调里直接setState更新页面状态就会触发setState() called after dispose()的异常。处理办法是在异步结果返回后先检查mounted属性final bool? result await _showSimpleDialog(context); if (!mounted) return; if (result true) { setState(() {}); }这个检查不是可选的是必须的。我在 OpenHarmony 上做低配设备适配时发现RK3568 由于内存有限Activity 重建相对频繁这类异步后置操作更容易踩到生命周期问题养成mounted检查的习惯能少踩很多坑。4.2 异步任务中的对话框处理除了简单的确认框还有一类常见场景点击按钮后发起网络请求或数据库操作期间弹出加载中对话框结束后自动关闭。这种对话框不能用AlertDialog因为它没有交互按钮应该用DialogCircularProgressIndicator组合。我提供一个可以反复使用的封装思路FutureT? showLoadingDialogT(BuildContext context) { return showDialogT( context: context, barrierDismissible: false, builder: (context) { return const PopScope( canPop: false, child: Dialog( child: Padding( padding: EdgeInsets.all(24), child: Row( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(width: 16), Text(处理中...), ], ), ), ), ); }, ); }调用时showLoadingDialog(context); try { final result await _apiService.doSomething(); if (!mounted) return; Navigator.of(context).pop(); // 关闭加载框 // 处理结果 } catch (e) { if (!mounted) return; Navigator.of(context).pop(); // 无论成功失败都要关闭 // 展示错误提示 }这里有一个体验上的细节加载框虽然不能通过遮罩关闭但如果页面里同时存在其他异步操作比如同时发起多个请求每个请求都把自己的 loading 弹窗 push 到路由栈里关闭时只 pop 一次就会导致弹窗残留。解决方式是给showDialog传入一个固定的 RouteSettings 名称关闭时精确匹配或者在业务层保证同一时间只会发起一个加载型弹窗。4.3 状态管理选型对对话框交互的影响如果你在项目里用了状态管理库对话框和全局状态之间的联动需要特别注意。以最常用的 Provider 为例class AppState extends ChangeNotifier { bool _isLoggedIn false; bool get isLoggedIn _isLoggedIn; void login() { _isLoggedIn true; notifyListeners(); } }在对话框内直接读取 Provider 是可行的因为对话框的 context 依然在MaterialApp的 Provider 作用域下。但更推荐的做法是把对话框的逻辑放到独立的 ViewModel / Controller 中这样对话框内部不需要关心业务状态的来源。以退出登录为例对话框只负责弹窗和收集用户的确认动作拿到 true 之后才去调用 LoginController 的方法而不是在当前页面的 setState 里直接改状态。针对 OpenHarmony 场景状态管理还有一个性能层面的考虑。RK3568 的 CPU 是四核 Cortex-A55主频最高 2.0GHz渲染复杂界面时压力不小。如果用一个全局大 Store任何一处状态变化都会触发局部setState整棵 widget 树都 rebuild卡顿感会很直观。多级拆分状态、让对话框这种局部组件自己持有局部状态比统一塞进全局 Store 的体验要好得多。5. 多设备适配与兼容问题5.1 RK3568 开发板上的布局避坑OpenHarmony 的设备形态跨度极大从手机到平板再到 RK3568 这种开发板屏幕尺寸和 DPI 差异非常大。对话框最常见的适配问题就是宽度溢出。Flutter 的AlertDialog默认宽度是 280dp在竖屏手机上看起来正好但在平板或开发板接的高分辨率显示器上会显得很小。解决方式是限制最大宽度ConstrainedBox( constraints: const BoxConstraints( maxWidth: 380, maxHeight: 500, ), child: AlertDialog(...), )反过来在小屏低分辨率设备上对话框内部文字过多时会出现溢出。排查溢出问题的核心工具是 Flutter 的 Debug 模式红黄条纹它会直接标出溢出的边界。不过这个可视化提示仅 debug 模式可见release 包上不会显示因此我通常会额外挂一层LayoutBuilder或者用FittedBox对长文本做缩放处理。另一个 RK3568 上的肉眼可见问题是对话框弹出动画的掉帧。showDialog默认动画是FadeTransitionScaleTransition由AnimationController驱动在低端 GPU 上弹窗的缩放动画能明显感到卡顿。建议在不追求华丽动效的业务场景下把对话框动画改成简单的淡入淡出或者直接设置为空动画Duration.zero对流畅度改善非常明显。5.2 Flutter 与 ArkUI 原生对话框的边界一个现实的问题是有些项目是混合开发部分页面用 Flutter 写但主框架是 OpenHarmony 的 ArkUI。这种情况下对话框的呈现出现了两个阵营要么 Flutter 自己实现一套要么通过 Platform Channel 调用原生 ArkUI 的promptAction.showDialog。我的建议是能用 Flutter 自己的对话框就尽量用 Flutter 的。原因有三视觉一致性Flutter 绘制的对话框不受原生组件主题影响在跨页面保持统一外观时优势明显。上下文共享Flutter 对话框的context可以直接使用上层 Provider 的依赖不需要做数据桥接。状态同步如果你的 Flutter 页面被 dispose 了对话框也会自动跟随路由栈销毁不用手动管理生命周期。只有在需要调用系统级能力比如 Touch Screen 上的权限申请对话框、系统升级确认框时才建议走 Platform Channel。OpenHarmony 的ohos.promptAction是一个常用的原生对话框接口通过 Flutter MethodChannel 调用时需要自己在原生侧实现方法override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { showNativeDialog - { val title call.argumentString(title) val message call.argumentString(message) promptAction.showDialog({ title: title, message: message, buttons: [ { text: 取消, color: 0XFF999999 }, { text: 确定, color: 0XFF0A59F7 } ] }) result.success(true) } else - result.notImplemented() } }这种混合方案我一般只在必须用到系统级样式或系统能力时才考虑日常业务弹窗全部走 Flutter 自绘性能和一致性都更好。5.3 深色模式与系统字体缩放OpenHarmony 系统的深色模式覆盖逻辑与 Android 类似系统切换时会触发 Flutter 框架的Theme.of(context).brightness变化。但有一个坑如果你在showDialog的 builder 里直接返回AlertDialog它默认会使用MaterialApp的theme而不是darkTheme。也就是说即使系统切到了深色模式对话框依然是浅色。这个问题在实际效果上很容易被忽略。在弹出的瞬间背景变暗了但对话框内容还是亮色底一眼就能看出没适配好。解决办法有两种第一种是给AlertDialog显式设置backgroundColor和textStyle让它根据Theme.of(context).brightness动态选择final isDark Theme.of(context).brightness Brightness.dark; AlertDialog( backgroundColor: isDark ? const Color(0xFF1E1E1E) : Colors.white, titleTextStyle: TextStyle( color: isDark ? Colors.white : Colors.black, ), ... )第二种更优雅是在MaterialApp上配置darkTheme和themeMode并确保dialogTheme在两个主题下各有一份。Flutter 框架会自动切换组件样式而不需要在业务代码里手动判断。系统字体缩放是另一个容易忽略的点。OpenHarmony 设置里允许用户调整字体大小当字体缩放比例达到 1.3 倍以上时对话框里的固定高度组件比如按钮区域很容易溢出。建议在自定义对话框组件里按钮区域的高度使用FittedBox包一下或者直接使用TextButton默认的高度不要手动指定SizedBox(height: 48)这类固定值给文字缩放留够余地。6. 常见问题与排查技巧实录6.1 常见问题速查表把我在实际调试中踩过的坑整理成表按出现频次排序问题现象根因解决方案对话框弹出后背景不暗barrierColor被全局主题覆盖在showDialog中显式设置barrierColor: Colors.black54点击遮罩层无法关闭barrierDismissible为 false按业务需要改为 true或在actions中提供取消按钮对话框按钮被键盘顶出屏幕AlertDialog未开启滚动设置scrollable: true或把 content 包在SingleChildScrollViewshowDialog多次调用导致抽屉多层弹窗用户重复点击触发用isDialogShowing标志位或RouteSettings名称管理弹窗状态对话框背景透明文字看不清AlertDialog的backgroundColor与文字颜色对比度不够使用预定义色板确保对比度高于 4.5:1关闭对话框后页面setState崩溃异步回调时页面已销毁在异步回调里加if (!mounted) return深色模式下对话框仍为白色未配置darkTheme.dialogTheme在MaterialApp.darkTheme下同步配置dialogTheme弹窗动画卡顿低端设备 GPU 渲染压力大设置transitionDuration: Duration.zero或改为FadeTransition中文文字显示为方块/乱码字体文件未正确加载在pubspec.yaml引入 HarmonyOS Sans 字体并设置fontFamily6.2 排查流程与日志定位遇到对话框相关的问题我的排查顺序一般是这样第一步先在桌面端或模拟器跑同样的代码排除是否是 Flutter 层本身的问题。如果 PC 上也复现那九成是代码逻辑问题与 OpenHarmony 无关如果 PC 上正常再转入真机 Debug。第二步查看真机日志。Flutter 的 debug 模式在flutter run控制台会输出 Dart 层的异常栈这是定位 index 越界、null 安全、setState 崩溃最快的手段。如果看不到日志大概率是调试连接断开重新执行flutter attach就行。第三步用 Dart 层的断言定位 UI 溢出。在AnalysisOptions里开启enable-experiment相关性检查后开发阶段 Flutter 会明确打印溢出区域的位置和约束信息直接把日志贴给 AI 工具或翻 Flutter 源码就能快速定位。这里分享一个我常用的技巧在对话框的关键生命周期方法里打日志比如initState、dispose以及在Navigator.pop前输出参数。这样一旦弹窗关闭后行为不对你能清楚知道是 dialog 内部的问题还是外部接收回调的问题debugPrint(Dialog dismissed, result: $result);6.3 性能优化的几个实测技巧低配设备上的对话框体验优化空间其实挺大的不需要上 profiler光靠几个经验值就能收到明显效果。首先尽量避免在对话框 builder 内部直接创建重量级组件比如图片加载、长列表。showDialog的 builder 会在路由构建时执行如果要展示的图片是网络图建议先加载到内存再弹窗否则弹窗会一直卡在加载中的状态。其次对话框内如果有TextField尽量设置autofocus: true而不是让用户点一下输入框才弹出键盘。免去一次点击是小事但对键盘弹出动画和布局重排的调用可以减少一次这在低端机上能减少一次明显的卡顿。最后如果你的AlertDialog里包含的只是静态文本和按钮完全可以用showDialog的useRootNavigator: true参数直接把弹窗挂到根 navigator 上。好处是弹窗可以覆盖到所有页面之上包括底部的 tab 栏不会因为当前页面嵌套了Navigator而只显示在局部区域。这在 OpenHarmony 分屏或嵌入场景下特别有用因为你无法预知宿主页面到底嵌套了多少层 navigator。7. 项目扩展与工程化沉淀7.1 把对话框封装成统一组件库对话框用的多了直接在每个页面里写showDialog就会产生大量重复代码。我在实际项目里会做一层封装把常用的对话框类型确认框、输入框、加载框、列表选择框统一抽象成静态方法放进一个DialogHelper.dart文件。class DialogHelper { static Futurebool? showConfirm( BuildContext context, { required String title, required String message, String confirmText 确定, String cancelText 取消, }) { return showDialogbool( context: context, builder: (context) { return AlertDialog( title: Text(title), content: Text(message), actions: [ TextButton( onPressed: () Navigator.of(context).pop(false), child: Text(cancelText), ), TextButton( onPressed: () Navigator.of(context).pop(true), child: Text(confirmText), ), ], ); }, ); } static Futurevoid showLoading(BuildContext context) { return showDialogvoid( context: context, barrierDismissible: false, builder: (context) const PopScope( canPop: false, child: Dialog(...), ), ); } }这样做的价值在于后续如果要统一替换对话框风格或者增加埋点统计、日志上报只需要改这一个文件全 App 的对话框行为都会联动生效。可维护性比在每个页面复制粘贴高得多。7.2 从对话框到完整 Flutter on OpenHarmony 项目结构对话框做完意味着你已经跑通了一个 Flutter 页面在 OpenHarmony 上的完整生命周期。这时候可以做两件事第一件把你现有工程的pages目录按业务模块拆分每个模块对应一个独立的 Feature 包包含页面、组件、状态管理和接口请求的各自文件。OpenHarmony 的设备资源有限按需加载比全部打包到入口更实际Flutter 的 deferred import 机制在这里能派上用场。第二件把开发模式切到 release 做一次全量性能验证。用flutter build hap --release打出正式包通过 hap 安装工具装到 RK3568 上重点检查首屏渲染时间、页面切换帧率和内存占用。Flutter 的flutter run --profile模式能拿到真机上的帧率曲线对话框的弹出动画如果掉到 20 帧以下就说明你的渲染链路里有重量级操作被弹窗触发了需要回到代码层面排查。7.3 后续扩展方向对话框只是起点。做完这一步你搭起来的 Flutter on OpenHarmony 开发链路已经具备向更多业务模块延伸的条件。比较顺延的扩展方向有这么几个列表页 下拉刷新 加载更多这是任何带数据的 App 最常用的形态Flutter 的ListView和RefreshIndicator在 OpenHarmony 上都有对应实现实测性能可接受图片加载与缓存借助cached_network_image这类成熟插件但要注意插件的依赖版本需要对齐适配分支的 Flutter SDK真机调试下的热重载优化掌握hot reload和hot restart的区别前者打热补丁、后者重建整棵 widget 树在 OpenHarmony 上混用可以显著提升调试效率。我在实际项目中初期只用 Flutter 写了不到十个核心页面后面陆续把设置页、扫码页和报表页也迁移了过来一年的时间里逐步形成了完整的 Flutter on OpenHarmony 开发闭环。回看第一个跑通的对话框功能它真正的价值不在于弹窗本身而在于验证了整套技术栈的可行性——跨平台代码复用是真的能做起来的OpenHarmony 生态也确实是 Flutter 开发者可以认真投入的方向。