☰
Flutter 跨端适配 OpenHarmony 实战:家庭药箱与体重记录实现
2026/9/29 17:15:03 网站建设 项目流程

接到这个需求的时候,我第一时间想到的是:终于有人要在开源鸿蒙生态里做正经应用了。项目标题看起来很简单——flutter_for_openharmony家庭药箱管理app实战+体重记录实现——但拆开来看,里面涉及的东西相当多:Flutter 跨端适配 OpenHarmony、本地数据持久化、列表与图表交互、平台通道桥接、打包签名调试。这篇文章我就按自己实际走过的流程,把这套东西从头到尾捋一遍,包括那些文档里不会写、但你真的会遇到并且卡很久的坑。

先说结论:Flutter 跑在 OpenHarmony 上,这事儿已经可行了,但还没到“开箱即用”的程度。你需要在环境、依赖、渠道三方面额外花功夫。本文的目标读者是两类人:一是想把现有 Flutter 应用迁移到鸿蒙生态的团队,二是从零开始想做一个纯 OHOS 本地应用、但不想碰 ArkTS 的开发者。家庭药箱和体重记录这两个模块,恰好覆盖了“轻量 CRUD + 本地图表 + 系统能力调用”三类典型场景,做完这一套,市面上大多数工具类 App 的核心路径你都能复刻了。

1. 为什么用 Flutter 做 OpenHarmony 药箱应用

1.1 项目缘起与技术选型

家庭药箱管理,本质上是一个“知道家里有什么药、放哪了、过期没”的轻量工具。常见功能无外乎药品列表、过期日期提醒、用药记录。这些功能单独看都不复杂,但组合起来就有一个共性需求:本地数据存储优先、离线可用、轻量交互。而体重记录则更简单一些,核心是输入体重数值、保存历史、画一条变化曲线。

这两个模块为什么要放在 Flutter 里做,而不是直接写 ArkTS?我当时的判断有几点:

第一,Flutter 的 UI 开发效率确实快。鸿蒙生态目前的原生开发以 ArkTS + ArkUI 为主,如果你熟悉的是 Flutter 的 Widget 体系和状态管理,转过去写 ArkUI 会有一段学习成本。而 Flutter 在 OpenHarmony 上的适配层flutter_ohos已经能跑通底层渲染和事件分发,这就意味着你写一套 Dart 代码,可以同时覆盖 Android / iOS / OpenHarmony 三个平台。对于一个家庭工具类应用,这是非常划算的。

第二,这个场景对系统底层能力的依赖较少。药箱管理不需要复杂的推送服务,不需要频繁唤醒系统 API,核心工作都在应用层完成。这类应用恰恰是跨平台框架最擅长的领域。如果你要做的是需要深度系统能力的高性能工具(比如录屏、底层网络抓包),那我不建议用 Flutter 上鸿蒙,除非你愿意花大量时间写平台通道去桥接。

第三,生态位。OpenHarmony 的应用生态目前属于早期,竞争相对小,但工具类应用的需求是刚性的。药箱管理和体重记录都是高频、稳定、长期使用的场景,做出来之后不会变成“一次性 Demo”,而是真的有人会持续用。所以就算跨端适配有些麻烦,也值得投入。

1.2 功能拆解:两个模块的边界划分

我建议在项目一开始就把功能边界划清楚,不要混着写。家庭药箱模块和体重记录模块虽然都在同一个 App 里,但它们的存储模型、页面结构和交互模式完全不同。

家庭药箱模块的核心数据是“药品”,围绕药品有几个子功能:

  • 药品添加与编辑(名称、规格、数量、有效期、存放位置)
  • 药品列表展示,支持按有效期排序和过期高亮
  • 过期提醒逻辑,需要计算当前日期与有效期的差值
  • 服药记录(可选,比如记录今天是否吃了某种药)

体重记录模块的核心数据是“体重快照”,它的逻辑非常简单:

  • 记录一条体重(数值、单位、当时的 BMI 指数)
  • 历史列表展示,支持删除误记录
  • 一段时间内的体重变化曲线

这两个模块的 UI 风格也会有差异。药箱管理偏数据型界面,适合列表 + 卡片组合;体重记录偏趋势型界面,适合图表 + 数字展示。后续我会详细说怎么在两个模块之间共享统一的主题和导航,但保持页面逻辑分离。

2. 环境搭建与工程创建,踩过的坑都在这里

2.1 OpenHarmony 侧到底需要准备什么

网上关于“如何用 Flutter 开发 OpenHarmony 应用”的资料很杂,很多是拿 HarmonyOS NEXT 的开发流程来混着说,容易把人绕晕。我理一下真实需要的环境组件,按顺序列出来:

  • OpenHarmony SDK:也就是ohos-sdk,包含 API 和工具链。开发阶段建议选 API 10 及以上的版本,API 12 目前适配度更好。
  • DevEco Studio:这是基于 IntelliJ 的 IDE,用来配置 OpenHarmony SDK 路径和打包签名。你也可以用命令行工具,但用 IDE 配置签名省心很多。
  • Flutter SDK:必须是你本机的 Flutter SDK,还有一个专门为 OpenHarmony 适配的 Flutter SDK 分支或补丁。这里有个关键点——并不是官方 Flutter SDK 直接就能编鸿蒙,需要给 Flutter 增加一个ohos平台target。

实际操作时,我在配置 OpenHarmony SDK 时遇到过版本对不上的问题。DevEco Studio 会自带或引导下载 SDK,但如果你本机 Android SDK 和 OpenHarmony SDK 同时存在,路径别搞混了。Flutter 项目里需要额外创建一个ohos/目录来承载 OpenHarmony 工程,这个目录结构类似于 Android 的android/目录。

这里我想重点强调一个容易被忽略的细节:OpenHarmony 和 HarmonyOS NEXT 并不完全等价。OpenHarmony 是开源项目,HarmonyOS NEXT 是商业发行版,虽然底层同源,但开发工具和支持范围有差异。如果你是给普通用户分发应用,需要根据目标平台选择合适的 SDK 和签名证书;如果只是想在开源生态里跑通应用,直接用 OpenHarmony SDK 即可。

2.2 Flutter SDK 版本与 ohos 适配层的坑

如果你使用 Flutter 官方最新版 SDK 去直接跑 OpenHarmony,大概率会看到一条类似警告:The current configured Flutter SDK is not known to be fully supported。这不是编不过,而是 SDK 版本与适配层的支持范围不一致。我在实际开发中用的是 Flutter 3.22 左右的版本配合flutter_ohos适配分支,整体比较稳定;如果你用到 3.24 以上,某些底层渲染可能有回归。

还有一个高频报错,报错信息长这样:

you are applying flutter's main gradle plugin imperatively using the apply script

这通常是你在构建ohos目录时,Gradle 脚本里给某个模块手动 apply 了 Flutter 插件,但 Flutter 的 Gradle 插件本身已经通过plugins方式加载,重复应用导致冲突。解决办法是回到ohos/settings.gradle和根目录build.gradle,检查是否有多余的apply语句,改成标准的plugins { id "com.flutter.gradle.ohos" version "..." }配置。

另一个我实际踩过的坑是 Gradle 依赖解析:

Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'. > Could not resolve all task dependencies for configuration ':app:debugCompileClasspath'.

这个问题的根源通常是依赖仓库顺序或者缺少特定的鸿蒙构件仓库。肖 fix 方法是在ohos/build.gradle里把mavenCentral()和鸿蒙的仓库地址都加上,并且确保没有把 Android 的仓库地址混进来。OpenHarmony 的构建系统虽然基于 Gradle,但它识别的是ohos插件,依赖解析与 Android 不完全一致。

经验小结:环境搭建阶段最重要的不是写代码,而是把 SDK 版本锁定。建议在项目的README里明确写清楚 Flutter 版本、ohos SDK 版本、DevEco Studio 版本,以及适配分支的 commit 号。不然过一个星期你自己都可能忘了当时用的哪个版本组合,换台电脑大概率会摔跤。

3. 家庭药箱管理核心功能实现

3.1 数据模型设计与存储选型

家庭药箱的数据模型,我最终设计成下面这样:

class Medicine { final int id; final String name; // 药品名称 final String spec; // 规格,例如 0.5g*24片 final int quantity; // 剩余数量 final DateTime? expiryDate; // 有效期 final String location; // 存放位置,例如 客厅药柜第二层 final String? notes; // 备注,例如 饭后服用 }

为什么字段要这么设计?核心是抓住“药品管理”场景下的三个关键动作:找得到(location)、看得懂(spec + notes)、记得住(expiryDate + quantity)。

存储方案上,我在 sqlite 和 Hive 之间对比后,最终选择了 sqlite。原因有两点:一是药品数据天然是结构化关系型数据,后续如果要增加“用药记录表”做关联查询,sqlite 直接支持 SQL 语法,扩展起来方便;二是sqflite插件有 OpenHarmony 适配版本,不需要自己写太多原生代码。Hive 更适合键值对密集的场景,但做不了复杂的范围查询,比如“查出所有三个月内过期的药品”,用 SQL 一句WHERE expiry_date BETWEEN ? AND ?就搞定了。

这是我在实际写表结构时的建表语句:

CREATE TABLE medicines ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, spec TEXT, quantity INTEGER DEFAULT 0, expiry_date TEXT, location TEXT, notes TEXT );

有一点要提醒:日期字段我建议用 TEXT 存储 ISO8601 字符串,而不是直接用 INTEGER 存时间戳。原因是在 SQL 查询时,TEXT 格式可以直接按字典序比较大小,例如expiry_date > '2025-03-01'是合法且可读的;而时间戳虽然也可以比较,但可读性差,调试时你一眼看不出那条数据是什么日期。

3.2 药品列表、到期提醒与高亮逻辑

药品列表是药箱模块的主界面,我的设计是上下两层结构:顶部是一个横向滚动的“状态统计卡片”区域,下面是按有效期排序的药品列表。

状态卡片展示三个指标:总药品数、临期药品数(30天内到期)、已过期药品数。这三个数字都是通过 SQL 聚合查询得出的,不需要在 Dart 侧写循环统计逻辑。

列表的排序规则是:已过期的最靠前,其次按有效期从近到远正序排列,没设置有效期的排最后。在 Dart 里我写了一个排序函数:

List<Medicine> sortMedicines(List<Medicine> items) { items.sort((a, b) { bool aExpired = a.expiryDate != null && a.expiryDate!.isBefore(DateTime.now()); bool bExpired = b.expiryDate != null && b.expiryDate!.isBefore(DateTime.now()); if (aExpired != bExpired) return aExpired ? -1 : 1; if (a.expiryDate == null) return 1; if (b.expiryDate == null) return -1; return a.expiryDate!.compareTo(b.expiryDate!); }); return items; }

每条药品卡片上,我会用背景色来提醒状态:过期药品卡片背景带浅红色、30 天内临期带浅黄色、正常药品维持白色。这里的颜色判断我用了一个独立函数,保证逻辑单一、可测试。

这里有一个交互细节值得讲讲:临期提醒卡片。我一开始做的是只在列表顶部放一行文字“有 X 种药即将过期”,后来发现用户根本不会去看。后来改成在 App 启动时弹一个非阻塞的底部弹窗,列出临期药品名称和剩余天数,体验好了很多。弹窗要设计成可一键跳转到对应药品详情。

3.3 页面导航与状态保持的正确姿势

药箱模块涉及多个页面:列表页、药品详情页、添加/编辑页、过期提醒弹窗页。我在导航上用了Navigator 1.0的经典 push/pop,没有引入go_router。为什么?因为药箱模块的导航路径很浅,基本是两层结构,不需要深链,也不需要路由守卫,手动管理栈反而最可控。

这里有一个群里经常讨论的问题:Navigator 切换页面后,原页面状态会不会丢失?答案是:默认会,但可以控制。

在我的场景中,药品列表页每次从详情页返回时,需要刷新列表数据,因为用户可能在详情页删除了药品或修改了数量。这个刷新逻辑我放在Navigator.push(...).then(...)回调里,返回后重新查询数据库。但对于另一个状态——列表的滚动位置——我希望它保留。实现方式是给ListView加PageStorageKey:

ListView.separated( key: PageStorageKey('medicine_list'), ... )

这样在 push 到详情页再返回时,列表滚动位置不会跳到顶部。

如果你的某个子页面内部有 TabBar,并且希望 Tab 之间切换时页面状态不丢失,那就要在 TabBarView 的孩子上包一层AutomaticKeepAliveClientMixin,这个在体重记录模块的“周/月切换”场景里我会用到,等下细说。

4. 体重记录功能的落地

4.1 记录模型与轻量级持久化

体重记录的数据模型非常简单:

class WeightRecord { final int id; final double weightKg; final double bmi; final DateTime recordedAt; }

BMI 计算并不复杂,公式是体重(kg) / 身高(m)的平方,但身高是一个用户设置常量,存在SharedPreferences里。这样每次记录体重时,BMI 可以自动算出来,不用用户再手动填。

存储选型上,体重记录我依然选择了 sqlite 的同一个数据库实例,只是单独建一张weight_records表。不建议用 SharedPreferences 存体重历史,虽然数据量不大,但后续你要做“按周筛选”“按月统计”,用 SQL 的WHERE recorded_at BETWEEN ? AND ?效率远高于遍历键值对。

体重的输入界面,我用的是一个全屏的无边框大号输入框,配合自定义数字键盘。为什么不用系统键盘?因为体重输入只需要小数点加数字,系统键盘占屏幕空间大,且容易误触。自定义键盘虽然开发量多一些,但体验提升非常明显。业界很多健康类 App 都是这么做的。

4.2 轨迹走势图:不引重量级图表库的方案

关于体重趋势图,很多人的第一反应是引入fl_chart这种图表库。但我要说:这个功能真的不需要。体重记录一周或者一个月才增加几条数据,用一个自绘的简单折线图完全够用。

我在项目里用CustomPainter画了一个轻量级折线图,展示最近 30 条记录的趋势。核心思路是:将记录列表按时间排序,映射为 Canvas 上的坐标点,然后连接成折线图。画图时需要注意几个细节:

  • Y 轴范围不要用“最小体重~最大体重”,那样曲线波动会显得非常夸张,用户看了容易焦虑。合理做法是把 Y 轴范围设置为“最小体重减 2kg”到“最大体重加 2kg”。
  • X 轴按时间均匀分布,还是不按时间均匀分布?这里我踩过坑。如果按时间均匀分布,用户某几天连续记录了多条,那几天的点会挤在一起;如果按“实际天数间隔”分布,则更符合直觉,但实现复杂一些。对于 30 条以内的轻量数据,我建议按时间均匀分布,简单且足够清晰。
  • 曲线要画平滑线,不要画直连线段。直连线段看起来像心电图,平滑线更像专业健康应用的风格。平滑线可以用贝塞尔曲线实现,不用引第三方库。

4.3 体重记录的列表交互与 Tab 切换细节

体重记录页我设计成分段视图:上半部分是趋势趋势图,下半部分是历史记录列表。历史记录列表支持左滑删除误记录,删除后图表要同步更新。

在一个页面里同时展示图表和列表,这里有一个性能问题:列表滚动时,图表会不会跟着重建?我的处理方式是把图表和数据列表拆成两个独立 Widget,图表用RepaintBoundary隔离,这样列表滚动时 Chart 不会反复重绘。

这里顺便把前面说的 TabBar 场景讲清楚。我最初加了一个“周/月”切换的 TabBar,用来切换图表展示的时间范围。如果 TabBarView 里的每个子页都带一个图表,那默认情况下切换 Tab,图表会重建一次,这没问题;但如果子页面里有列表,并且你希望列表的滚动位置在 Tab 切换后保持不变,就要用AutomaticKeepAliveClientMixin:

class MonthTabView extends StatefulWidget { ... } class MonthTabViewState extends State<MonthTabView> with AutomaticKeepAliveClientMixin<MonthTabView> { @override bool get wantKeepAlive => true; }

加了这层之后,Tab 切换时进度保存没问题,但如果你同时想要每次切换到该 Tab 时刷新数据,就得配合VisibilityDetector或者在 TabController 的监听里去手动调用刷新方法。这里是个取舍,实际项目里看你的场景更看重哪一边。

5. 平台交互:EventChannel、MethodChannel 与鸿蒙能力对接

5.1 三种通信渠道,用哪个、什么时候用

Flutter 与鸿蒙原生侧的通信,核心渠道还是那三个:EventChannel、MethodChannel、BasicMessageChannel。这个在移动开发圈是老生常谈,但在 OpenHarmony 上有些差异。

我做了个对比表格,方便你一眼选型:

渠道适用场景数据方向Flutter 侧体验
MethodChannel一次性请求,例如“获取系统电量”Flutter 调用原生,原生返回结果返回 Future,适合类似函数调用
EventChannel持续的事件流,原生主动推送原生 -> Flutter返回 Stream,适合监听类场景
BasicMessageChannel双向消息,格式可自定义双向持续通信类似 WebSocket 的体验,适合复杂交互

在家庭药箱应用里,我实际用到的其实是 EventChannel:我需要监听系统的“低电量提醒”事件,在低电量时提示用户“记得按时吃药”。这个提醒逻辑不做就会丢一个很有温度的场景。如果只是在 App 内部做提醒,用本地的Timer就够了,完全不需要碰平台通道。

5.2 通过 EventChannel 实时接收系统事件的完整实现

在 OpenHarmony 侧,EventChannel 的实现跟 Android 侧类似,但 API 名称有区别。我在 ohos 目录的MainAbility里注册了一个事件通道:

// 鸿蒙侧 import { EventChannel } from '@ohos/flutter_ohos'; let eventChannel = EventChannel('com.example.medicine/event/low_battery'); eventChannel.setStreamHandler({ onListen: (arguments, eventSink) => { // 监听系统电量事件,通过 eventSink.success(data) 推给 Flutter }, onCancel: (arguments) => {} });

Flutter 侧,我封装了一个服务类来订阅这个通道:

class BatteryEventService { static const _eventChannel = EventChannel('com.example.medicine/event/low_battery'); Stream<String> get lowBatteryStream { return _eventChannel.receiveBroadcastStream(); } }

需要特别注意的是,EventChannel 的通道名称必须在原生侧和 Flutter 侧完全一致,否则你会遇到事件完全接收不到的情况,且不会有明确报错。调试这个问题的技巧是:先在原生侧用日志确认事件确实被触发了,再在 Flutter 侧打印流事件,逐步排查是“没发出去”还是“没收到”。

5.3 通道命名规范与调试技巧,踩过高频问题都在这

通道命名这件事,看起来是一小步,做不好是打脸的一大步。官方建议是用域名反写的命名方式,例如com.yourcompany.app/channel_name。这种命名在跨端开发时特别重要,因为一个应用里可能有十几个通道,如果不规范,写到后面你自己都分不清哪个是哪个。

我实际遇到过一个问题:Flutter 侧MethodChannel调用鸿蒙原生方法时,返回值偶尔为 null。排查了很久,最后发现是原生侧返回了一个Map,但Map里的 key 是String类型而 Flutter 侧期望的是int类型,类型不匹配导致反序列化失败。经验是:跨端传递数据时,尽量只传 JSON 兼容的纯数据对象(String、num、bool、null、List、Map),不要传自定义对象或特殊类型。这个规则在 OpenHarmony 侧同样适用。

如果你的项目后续要扩展大量平台能力,建议把通道相关的类统一放在一个platform/目录下面,并给每个通道写一个独立服务类。比如BatteryEventService、NotificationService、SensorService。这样业务代码里不会到处散落魔法字符串,后续排查也好定位。

6. 打包、性能与常见问题排查实录

6.1 OpenHarmony 打包流程与权限配置注意事项

OpenHarmony 应用打包和 Android 大致相似,但有几个点不一样,值得单独写出来。

第一个是签名。在 DevEco Studio 里配置好签名证书后,你打包出来的 HAP 或者 APP 文件才能安装到真机上。如果是开发自用,可以用自动签名;如果要分发,需要申请发布证书。这里的细节是:签名配置是跟着 DevEco Studio 项目的,如果你的 Flutter 工程因为ohos目录是后补的,还需要确认 Flutter 工程里的ohos目录能被 IDE 正常识别和关联到 Dart 代码。

第二个是权限配置。药箱应用需要什么权限?坦白说,本地存储和 UI 展示都不需要特殊权限。但如果你加入了“通知栏提醒”功能,就需要在module.json5里声明通知权限。有一个容易踩坑的地方:某些设备上通知权限默认是关闭的,你需要主动引导用户开启。鸿蒙的权限列表和 Android 不完全一致,不要直接把 Android 的权限声明搬过来。

第三个是打包命令。Flutter 构建产物到鸿蒙包,跟在 Android 上略有不同。我在实际项目里是先构建 Flutter 的 AOT 产物,然后由 DevEco Studio 构建 HAP。如果你全程命令行操作,需要注意hvigor的配置,否则容易出现 “无法找到 Flutter 动态库” 这类问题。

6.2 性能优化:Impeller 渲染带来的改变

性能这个话题,几乎每个 Flutter 开发者都会关注。在 OpenHarmony 平台上,Flutter 默认使用的渲染引擎是 Impeller。我在实际体验中发现,打开 Impeller 之后,列表滚动时的帧率稳定度明显更好,滑动过程中的掉帧明显减少,尤其是在药箱列表这种带卡片阴影效果的页面上,视觉效果更平滑。

有一点要提醒的是:Impeller 和 Skia 渲染在某些图形特效上表现不同。我发现在 OpenHarmony 上,如果你的自定义CustomPainter用了复杂的BlendMode(混合模式),Impeller 的表现可能跟 Skia 不同,导致画面出现意外效果。如果你遇到了“自己画的东西跟预期不一致”,先检查是不是 Impeller 导致的。这个排查方向在官方文档里写得不是特别清楚,我自己是被坑过两次才发现的。

另一件值得做的事,是关闭或自定义 TabBar 的点击动画。我看网上好多人问“flutter tabbar点击取消动画效果”,实际做法是在TabBar的indicator配置里去掉动画,或者通过TabController手动控制切换。如果你做的体重记录页有“周/月”切换,动画反而可以保留,因为那是 Tab 切换,不是 TabBar 到 TabBar 的跳转,保留动画会让过渡更自然。但如果你的药箱列表里有“筛选”Tab,那动画就可以去掉,减少不必要的视觉噪声。

怎么自定义呢?最简单的方案是:

TabBar( controller: _tabController, indicator: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(8), ), ... )

indicator的默认实现是带平滑动画的,如果你给indicator传了自定义BoxDecoration,动画行为会有所不同,但整体的点击切换动效依然存在。如果你想完全取消动画,需要把TabBar换成自定义按钮组,自己维护选中状态。这个方案最可控。

性能优化的最后一个建议是:列表页的 item 尽量用const构造,并在 item 内避免复用复杂的 GDI 对象。在 OpenHarmony 上这一点跟 Android 一样,但更敏感,因为有些真机的 GPU 性能不如主流手机。我发现药品列表的卡片阴影如果太重,真机上滚动会有一点点噪点感。阴影改为浅色和浅范围后,观感明显干净了。

6.3 常见报错与排查速查表(都是实战中验证过的)

把我在这个项目里遇到过的、以及群里高频出现的报错整理成一张表,方便你对照参考:

报错关键字核心原因排查建议
Main Gradle Plugin applied imperativelyFlutter Gradle 插件重复应用检查ohos/build.gradle是否有重复apply
Could not resolve task dependencies依赖仓库配置缺失或顺序错误在ohos/build.gradle中补全仓库地址
Flutter SDK is not known to be fully supportedFlutter 版本与 ohos 适配分支不一致锁 pin 一个已验证的 Flutter 版本组合
Could not close input stream构建缓存损坏或者文件占用清理.gradle缓存,重点检查 Windows 环境
原生回传数据解析失败跨端传递了不兼容类型统一用 JSON 兼容的纯数据类型

在这些报错里,最不值得浪费时间的就是 “Flutter SDK is not known to be fully supported” 这一类警告。它只是一个提示,告诉你当前组合未经官方完整验证,但不代表不能用。我一开始看到这个警告就疯狂换版本,后来发现完全没必要,只要功能正常、测试通过,该用还是用。但是,如果你打包时遇到报错,而且报错信息里出现了 SDK 版本字样,那就要认真对待版本一致性问题了,这是两码事。

结尾的几点实在话

这个项目做下来,最大的体会是:Flutter 上 OpenHarmony 并不是“把 Android 的配置改名复制一份”就行,它需要你对 Flutter 的跨端抽象有更深的理解,尤其是平台通道和构建链路的差异。家庭药箱和体重记录这两个模块,难度不算高,但胜在覆盖面广,能帮你把 Flutter + OpenHarmony 的主流开发路径整个走通一遍。如果后面你有更多想法,比如把药箱数据通过云同步到家人手机上,或者加入体脂、血压等更多健康指标的记录,这个项目的地基是能撑得住的。留在手里的这套代码和这份踩坑清单,起码能让你下次起新项目的时候,少走一整晚的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询