1. 项目定位与整体架构思路
先交代清楚这个项目是干什么的。某高校日常有大量勤工俭学岗位需求,比如图书馆整理、实验室值班、行政助理、食堂秩序维护这些,过去靠辅导员群发通知、学生填纸质申请表,信息来回流转效率非常低。这个Flutter × OpenHarmony的App就是把这些流程线上化,覆盖学生端和用工部门端,岗位发布、浏览、报名、审核、录用通知全链路打通。技术栈选了Flutter和OpenHarmony搭配,市面上针对这个组合的完整实践记录不算多,很多细节要靠自己趟,所以我决定把从数据建模到页面落地的全过程写出来。
1.1 为什么选Flutter × OpenHarmony
先说Flutter。团队里有现成的Flutter基础,前期在手机端已经有一套页面原型,如果传统安卓和iOS各写一套原生界面,工作量直接翻倍。Flutter的跨端特性可以让我维护一套Dart代码,同时覆盖移动端和新的操作系统场景。OpenHarmony这边,核心诉求是适配国产操作系统生态,但直接上用ArkTS重写全部业务并不现实,工期和人都扛不住。于是采用了Flutter作为UI框架层,OpenHarmony作为系统底座,通过标准平台通道去调系统能力。
这个组合的收益很直接:业务代码只要写一次,页面结构、数据模型、交互逻辑全部复用。真正的差异点集中在系统能力调用层,比如选图、文件存储、网络权限这些。项目里我把这些差异隔离在统一接口后面,上层完全感知不到跑在哪个系统上。当时评估下来,这个方案相比双端原生开发省了大概四成工作量,尤其是列表页和表单页这种高频页面,收益非常明显。
1.2 工程分层与模块边界
工程结构上我参考了常见的分层架构,没有搞得很重,但边界一定要清晰。整体拆成四层:UI表现层、业务逻辑层、数据仓库层、系统能力层。
UI层只负责Widget布局和用户交互,不掺和任何业务判断。比如首页岗位卡片,它只管把JobPost对象渲染成卡片样子,点击回调传给上层,至于是否能报名、剩余名额是否足够,那属于业务逻辑层的事。业务逻辑层处理状态流转、表单校验、接口调用编排,用了Riverpod作为状态容器。数据仓库层屏蔽数据来源,上层不关心数据是来自远程接口、本地数据库还是内存缓存。系统能力层封装了OpenHarmony的平台通道,比如获取设备信息、选择图片、读写文件。
这个分层方式让项目坑少了不少。刚开始写的时候脑子热,直接在Widget里塞接口请求,结果页面一刷新状态全乱。后来硬性规定:Widget里不允许出现网络调用、数据库访问、平台通道,全部收拢到业务逻辑层和技术服务里。再往后新加页面,只需要复制已有的页面模板,往上套数据模型,开发速度明显提起来了。
1.3 数据流方向:从接口到页面的单向流动
整个App的数据流是单向的。用户触发操作,业务逻辑层的控制器拿到事件,调用数据仓库接口,数据仓库返回结果后更新状态容器,状态容器变化通知被监听的Widget重建。这个流程类似Flutter官方的状态管理思想,但我在中间加了两个细节。
第一,所有异步操作都拆成加载中、成功、失败三个状态。页面不能只在成功时刷新,失败时也要给用户反馈。第二,列表数据区分首屏加载和分页加载,首屏失败可以整页重试,分页失败只显示底部加载错误的提示条。刚开始没有这两个约束,列表页经常出现“下拉刷新后白屏”“翻页失败后一直转圈”的问题,后来把数据状态统一收敛成枚举,每个页面都遵守同一套约定,问题就很少再冒出来了。
2. 数据模型设计:从岗位字段到状态机
数据模型是整个项目的根基。Flutter页面再多,本质上是把数据映射成UI展示。我现在回过头看,很多改起来想骂人的问题,根子都在数据结构一开始没设计好。勤工俭学App里的数据不算多复杂,但涉及岗位、人员、申请记录多个实体,实体之间的关系和状态流转如果模糊,后面页面联动就非常痛苦。
2.1 岗位数据模型:字段设计背后的取舍
岗位数据模型是核心,直接决定首页卡片、详情页、发布表单长什么样。我定义的岗位字段主要分四类:基础信息、薪酬信息、状态信息和冗余信息。
基础信息包括岗位编号、岗位名称、用工部门、岗位类型、工作地点。岗位编号这个字段我坚持要了,格式类似GW20241108-01,前面是岗位类型缩写,中间是发布日期,后面是当日序号。业务上学生报名和后续核销都靠这个编号对账,比自增ID直观很多。岗位类型我用了枚举,包括图书管理、实验室助理、行政值班、食堂协助、活动支援等,首页筛选和搜索都依赖它。类型字段一开始用字符串,后来发现写“图书馆整理”和“图书管理”的人都有,数据脏得一塌糊涂,改成枚举加后端字典后就干净了。
薪酬信息包含计薪方式、金额和结算周期。计薪方式有按时、按天、按月三种,页面展示时统一渲染成“18元/小时”“120元/天”这样的格式。金额字段我用整数存储,单位是分,显示时再转成元。这是电商系统常用的做法,避免浮点数精度问题。虽然勤工俭学金额不会涉及太多小数运算,但统一用分存储之后,统计报表和结算模块计算就简单了。
状态字段尤其重要。岗位状态分成招募中、已满员、已结束、已下架四个枚举。已满员是招募中达到名额上限后自动进入的,已结束是报名截止时间过了或者用工部门主动结束的,已下架是管理员撤下来的。这个状态机关系到页面能否展示报名按钮。招募中显示“立即报名”,已满员显示“名额已满”,灰色置灰。这个逻辑必须在后端校验,前端只是展示。即使接口请求未被拦截,后端也要拒绝,现在很多安全漏洞都是前端隐藏按钮但接口照常能调出来的情况。
冗余字段我存了报名人数、剩余名额和收藏数。这三个字段理论上可以通过count查询实时算出来,但放岗位表里能用一条查询语句搞定列表页展示,避免每次都去关联申请记录表做聚合。代价是每次申请通过或撤销时要同步更新计数,我在事务里一起处理,保证数据不会出现“岗位显示还有3个名额,但报名人数已经超过岗位人数”的矛盾。
2.2 用户与申请记录的状态机设计
用户实体比较简单,学生侧字段主要就是学号、姓名、学院、年级、联系电话、可工作时间标签。可工作时间标签是给岗位推荐用的,比如“周一上午有空”“周末全天”,用逗号分隔存储。用工部门侧的账号对应部门名称、审核人、岗位发布数量等字段。
申请记录是整个系统里状态流转最复杂的部分。我把它设计成了五个状态:待审核、已录用、已拒绝、已撤销、已完成。待审核是学生提交报名后进入的初始状态。用工部门看到待审核记录后,可以选录用或者拒绝。已录用之后,如果学生因为课业冲突不想去了,可以在用工部门确认完成之前主动撤销,撤销后申请回到已撤销状态,岗位名额自动释放回岗位表。岗位约定的工作完成后,用工部门在管理端确认完成,申请记录进入已完成状态。
这个状态机的设计思路可以类比快递订单,从下单到签收有一套固定流转路径。好处是前端页面只需要按状态分Tab展示,后端也只需要对允许的状态迁移做校验。比如纯粹写页面的话,待审核卡片和已录用卡片就好区分:待审核显示“审核中”,灰底;已录用显示录用时间和工作安排,绿底。但如果没有状态机约束,可能出现已拒绝的申请又被改成已录用,或者已完成的岗位还能申请撤销,页面逻辑就乱了。所以我在后端的申请接口里做了一层状态迁移校验,不是目标状态就返回错误码,前端配合弹出提示。
2.3 本地缓存与接口模型的一致性处理
勤工俭学App有大量列表页,总不能每次打开都去请求网络。我在数据仓库层做了缓存策略:岗位列表接口首次加载成功后写入本地数据库,后续启动App先读缓存渲染,再静默请求最新数据更新界面。
这里必须处理一个关键问题:缓存数据结构和接口返回结构不一致怎么办。后端接口返回的字段名是snake_case,比如post_title、work_location,Dart侧更习惯用camelCase的postTitle、workLocation。我写了Model层的fromJson和toJson做字段映射,整套映射逻辑放在数据仓库层完成,页面接触到的永远是干净的数据模型对象。
另一个麻烦是缓存过期问题。岗位数据不像新闻资讯,岗位下架和名额变化非常频繁。如果学生打开App用的是昨天的缓存,看到的热门岗位可能已经下架了,点击去报名才提示“岗位已结束”,体验非常差。我的处理是:列表详情页入口做一次实时校验,详情接口返回岗位当前状态;首页下拉刷新时强制跳过缓存请求最新列表;本地缓存的岗位状态字段如果显示为已结束但详情返回招募中,用详情数据覆盖缓存并提示用户下拉刷新。这套组合拳打下来,缓存和接口不一致的问题基本可控。
3. 页面构建实战:五张核心页面的数据驱动方式
页面构建是项目里最直观的部分,但真正考验功底的是如何让页面和数据模型对齐。我做了五张核心页面:首页岗位流、岗位详情页、申请记录页、发布页和个人中心。每张页面都有各自的特色和要注意的坑,分开说。
3.1 首页岗位卡片流:列表复用与分页加载
首页是整个App的门面,也是性能要求最高的页面。岗位卡片我用了ListView.builder构建,卡片内包含岗位名称、用工部门、薪酬标签、岗位类型Tag和状态信息。ListView.builder按需构建可见条目,配合固定类型的Card组件,滑动实测非常流畅。
分页加载是首页的关键。每页返回10条,按发布时间倒序。滚动位置超过内容总高度80%时触发下一页加载。我维护了一个loadingFlag,防止滚动过快时重复触发加载。加载中在列表底部显示转圈占位,加载失败显示“加载失败,点击重试”的占位条,加载完成后重新计算是否需要继续加载下一页。分页游标用的不是页码,而是最后一条岗位的发布时间。这是为了避免新发布的岗位插入列表头部时,下一页数据出现错位和重复。如果只用page=2去翻页,第一页刷出新数据后第二页的10条可能和第一页重复了。
下拉刷新用的是RefreshIndicator,刷新时重置游标,清空列表数据重新加载。这里我踩过一个坑:直接清空列表再加载会导致刷新过程中屏幕闪白。解决方法是刷新期间保留旧列表,等新数据返回后再整体替换,视觉上平稳很多。另外卡片状态标签颜色用不同色值区分,招募中是蓝色,已满员是灰色,已结束是深灰色,人工筛选时一眼能分辨。
3.2 岗位详情页:报名交互与字段渲染
详情页在首页点击卡片进入,数据由岗位ID从接口获取。页面结构从上到下分为标题区、薪酬信息区、时间地点区、岗位描述区和报名操作区。薪酬和时间地点是学生决策最重要的信息,我做成了两行三列的网格展示,比如薪资、岗位类型、报名截止时间放一行,工作时间、工作地点、所属部门放第二行。
报名按钮是详情页唯一的核心操作按钮。点击后先判断登录态,未登录则跳转登录页。已登录则弹出确认框,展示岗位名称、薪酬标准、学生学号和姓名,确认后调用报名接口。这里一个关键处理是:报名接口返回后必须重新拉取岗位详情,更新剩余名额,因为并发环境下本地拿到的名额数据很可能已过期。
详情页还要处理岗位下架后的展示。如果接口返回岗位状态是已结束或已下架,报名按钮隐藏,显示“该岗位已截止报名”的提示条,同时禁用页面底部所有操作。我用的方案是详情页整页监听岗位状态,状态变化后通过Riverpod的ProviderScope刷新相关组件。如果只把这个判断写在initState里,用户从后台切回App时状态可能已经变了,页面还是要重新检查一次,所以我在didChangeAppLifecycleState里也加了一次状态刷新。
3.3 申请记录页:状态Tab联动刷新
申请记录页是学生查看自己报名情况的地方。我用TabBar分成了四个Tab:全部、待审核、已录用、已结束。每个Tab一个独立的ListView。这里最容易被坑的是嵌套滚动冲突,如果把每个Tab的列表放在PageView里,再整体套一个可滚动容器,两个滚动方向会打架,体验很奇怪。我的做法是TabBarView里每个Tab页是独立滚动容器,不额外嵌套外层滚动。
四个Tab的数据来源是同一个申请列表接口,只是参数不同,用status字段区分。状态切换时重新请求对应列表。数据返回后,每个申请卡片展示岗位名称、申请时间、状态标签和操作按钮。待审核状态下只有“撤销申请”一个操作,已录用状态显示录用通知内容和“确认参加工作”的按钮。这里有个隐藏联动逻辑:申请记录页的撤销操作成功后会释放岗位名额,如果此时用户回到首页看到之前的岗位,名额应该已经更新。所以申请记录页撤销成功后,我会触发一个全局刷新事件,首页监听并重新拉取列表。这个联动如果不做,用户会看到“岗位明明还有名额,但点进去又说已满”的诡异现象。
3.4 发布与个人中心页:表单联动校验
发布页给用工部门用。表单字段包括岗位名称、岗位类型、工作地点、工作时长、薪酬标准、招聘人数、报名截止时间、岗位描述。发布页的核心是表单校验,我在每个字段上挂了校验器,岗位名称不能为空且长度不能超过30个字,薪酬必须大于0,招聘人数必须是正整数,报名截止时间必须晚于当前时间。所有校验通过后才允许点击发布按钮。
个人中心页展示登录用户的学号、姓名、学院、年级和可工作时间标签,同时提供编辑资料、我的收藏、我的申请、消息中心等功能入口。这里技术含量不算高,但要注意个人信息展示从本地缓存读取可能已经过期,每次进入页面要从接口刷新一次。可工作时间标签用的是ChoiceChip组件,用户选择后用逗号拼接存到服务端,再编辑时按逗号分割还原选择状态,细节处理好后用户改信息不会觉得莫名其妙。
4. 状态管理与多端适配的关键处理
状态管理选型和多端适配是整个项目最纠结的部分。Flutter社区方案很多,但没有一个银弹,关键看团队是不是能稳住一套方案用到底。OpenHarmony侧的适配更是没有现成答案,全靠摸索。
4.1 状态管理选型:轻量Provider还是Riverpod
项目初期我对比了Provider、Riverpod和Bloc三个方案。Bloc的样板代码太多,页面简单时写起来感觉非常笨重,打个点要写一堆event和state,不划算。Provider虽然轻量,但依赖InheritedWidget隐式传递,项目变大以后找依赖关系比较费劲。
最后选了Riverpod,原因有三个。第一,它编译期就能检查ref使用是否合法,不会运行时才报“ProviderNotFound”,这一点在项目迭代中帮我省了大量排查时间。第二,异步逻辑可以直接用FutureProvider管理,不用手动维护loading状态,岗位列表、详情、申请记录这些接口直接映射成异步状态。第三,ProviderScope的层级结构清晰,测试时注入mock数据非常方便。
实际使用中,Riverpod让我最满意的是自动重建机制。岗位详情页面修改数据后,依赖这个Provider的其他组件会精确重建,不会整个页面都闪。不过我也踩了坑:在列表页的分页加载里,用FutureProvider维护列表数据时,加载下一页需要读取当前state里的旧数据并拼接。Riverpod的state默认不可变,必须通过NotifierProvider修改,这个稍不注意就会写成每次加载下一页都从第一页开始。我的解决办法是自定义一个延续类型的AsyncNotifier,把分页状态和列表数据封装在一起,提供加载更多方法,由外部调用。
4.2 平台通道:OpenHarmony系统能力接入
Flutter侧页面写好后,OpenHarmony的系统能力调用是绕不过去的。这个Project里至少三个能力需要走平台通道:选择图片、读取文件路径、获取设备信息。Flutter官方插件多数兼容这个组合,比如网络请求插件、本地数据库插件,但选图器这类涉及系统UI的能力,当时在OpenHarmony上没有官方插件,只能自己写。
我封装了一个选图器的MethodChannel,通道名定义成app/utilities。Flutter侧用异步方法调用,参数是图片数量限制,返回值是图片文件的本地路径。OpenHarmony侧用ArkTS实现接口,调用系统Picker拉起选图界面,拿到结果后把路径转成字符串返回。这个过程中有个坑:Flutter侧的result类型必须是String类型,OpenHarmony侧返回的是Uri对象,直接传回去会报类型转换错误,必须在端侧先把Uri转成路径字符串再回传。
网络权限倒不用走通道,在OpenHarmony的配置里声明网络权限就能联网。但文件读写路径和安卓差异很大,path_provider插件在OpenHarmony上返回的目录结构和安卓完全不同,导致我调试缓存目录时一度找不到文件落哪了。最后我打日志把返回路径打印出来,对照端侧应用沙盒目录结构才搞清楚。这个经验就是:碰到路径问题先打日志,别靠猜。
4.3 多端差异清单:UI、存储、输入法
多端适配不是一次性的工作,我把整个开发过程中遇到的差异点整理成清单,后面做新页面直接对照排查。UI层面最大的差异是字体渲染,OpenHarmony上文字默认稍靠上,显得偏细,页面里中文长文本截断位置和安卓不完全一致。我的解决方式是统一使用系统字体,不加载第三方字体包,同时给文字留出足够行高和边距。
存储差异也很明显。安卓的SharedPreferences在OpenHarmony上对应的是一套分布式键值库,接口名称不同。我在数据仓库层做了一个KV存储接口,分别实现两个平台的适配器,页面层不感知底层存储差异。这个抽象成本不高,但避免了以后把App迁移到别的平台时到处找存储调用点。
输入法弹起问题属于经典的跨端老大难。安卓上默认adjustResize行为,OpenHarmony上部分版本会遮挡输入框。我的表单页都包了SafeArea和resizeToAvoidBottomInset: true,发布页和登录页实际测试下来键盘遮挡问题处理得还行。但日期选择器这种弹层组件,在部分OpenHarmony版本上有显示层级异常的问题,我把弹层入口改成从页面根部推入一个全屏半透明遮罩,避开了系统弹层层级的不一致。
5. 性能优化与问题排查实录
页面能跑通只是第一步,上线前我把性能问题集中处理了一遍。Flutter页面性能问题很典型,集中在列表流畅度、图片加载和重建范围这三块。另外记录了一些开发过程中高频遇到的报错,整理成速查表放项目文档里,效率提升很明显。
5.1 列表滑动流畅度优化
首页岗位卡片流起初滑动有点掉帧,尤其在低端设备上更明显。我用性能分析工具定位到两个主要问题。第一,卡片里每个条目都创建了新的函数对象传给onTap回调,导致列表滚动时每个Widget的canUpdate判断失效,大量条目重建。解决办法是把onTap回调提取成单个方法,用岗位ID区分点击目标,多个条目共用同一个函数对象。第二,卡片内部用了一个Column包三个子组件,每次滚动都重新计算布局,改成固定的Row和Expanded组合后,好很多。
另外我对列表条目做了scope优化,用SizedBox固定了卡片高度。岗位卡片结构相对规整,标题两行限制、描述信息不额外扩展,高度可以预估,我设置了估算高度作为itemExtent的替代方案。固定高度后ListView的布局效率提升非常明显,滑动到300条以后也没有明显卡顿。
列表底部加载提示也要优化。转圈占位组件和加载失败组件是两种不同结构,如果放在同一个ListView的列表项里,状态切换时会触发重建。我把底部提示做成了一个独立组件,根据加载状态参数切换内部样式,结构尽量保持稳定,减少重建量。这个小优化平时不注意,但长时间滚动列表时会体感明显。
5.2 图片资源与缓存策略
勤工俭学岗位会配一张工作环境的展示图,学生头像也有图片。图片这块我用的是cached_network_image插件,解决默认Image.network不缓存的问题。列表页的缩略图和详情页的大图使用不同的宽高请求参数,后端会根据参数输出不同尺寸,有效减少列表页的流量消耗。
图片加载顺序也做了优先级调整。首页卡片加载时,首屏可见的图片优先加载,滑出屏幕的图片会释放内存。这个在Flutter里靠CacheWidth参数实现,给缩略图组件传一个100的CacheWidth,强制解码时按100像素宽处理,内存占用大幅下降。这里最开始的坑是没设CacheWidth,列表页加载40多张图片后内存直接飙到三百多兆,应用被系统杀掉,设置后内存降到一百多兆,问题解决。
线上环境还有一个细节:图片的404返回会反复触发重试,导致列表页滑动时网络请求频繁。我在图片加载组件里加了错误占位图,同时设置一个加载失败标记,避免同一个URL反复请求。
5.3 典型报错与排查思路速查表
我把开发中遇到的报错和解决思路整理成了速查表,这里挑几个有代表性的列出来。第一个是编译版本不匹配,Flutter SDK和OpenHarmony SDK的版本要求严格对应,版本不一致时编译大概率报错,且报错信息很晦涩。这个只能按官方版本对应表核对,没有捷径。第二个是MethodChannel方法找不到,通常是端侧通道注册时机错误或通道名拼写不一致,排查时两端都打印通道名对比。第三是列表分页后数据重复,多半是排序条件不够唯一,加一个自增ID作为辅助排序就能解决。
表格式的汇总放在项目wiki里,新同学接手时先看这个表,能少踩很多坑。我始终觉得,排查问题的能力不在于看多少源码,而在于能不能熟练使用日志定位边界,把问题缩小到特定模块。项目后期,我给自己定了两个规矩:遇到奇怪现象先开日志,确认是不是缓存里的旧数据;所有网络请求都在控制台打请求参数和响应,确认数据流动方向是否符合预期。
6. 跨平台打包与上架适配踩坑
这个章节是项目上线前的最后一段路,也是最容易被忽视的部分。打包和系统权限这一层问题比较琐碎,但出错会直接影响应用能否正常安装和使用。我记录了几个关键点。
6.1 构建产物与签名配置
OpenHarmony应用的打包产物和安卓不太一样。安卓是APK或AAB,OpenHarmony是HAP包。打包流程需要用到对应的构建工具,配置签名后才能生成可安装的包。一开始我没注意SDK版本对应关系,用较新的SDK打好包之后,安装到测试设备上报“安装失败”,排查了半天才发现是编译器版本和设备的系统版本不匹配。
签名配置也是一个容易忽略的细节。OpenHarmony的签名文件和安卓的keystore完全不是一个体系,不签名或者签名错误,应用安装不上。调试期可以用自动签名,但上线前必须换正式签名。签名的私钥一定要妥善保存,我在本地密码管理工具里存了一份,项目文档里再放一份提醒。团队如果有成员离职,签名文件丢失就要重签,用户侧就得卸载重装,很麻烦。
打包的时候我还踩了一个包体积的坑。Flutter debug包体积接近300MB,release包也要150MB左右,对OpenHarmony应用来说偏大。我用资源压缩工具清理了无用的字体文件和示例图片,再配合代码混淆,release包降到100MB以下。这个优化对于下载渠道的转化率还是有意义的。
6.2 权限声明与隐私合规
OpenHarmony的权限声明在配置文件里,和安卓的AndroidManifest不同。网络权限必须显式声明,不声明就断开网络。文件读写权限根据用途区分,如果只是读取临时缓存,可以不申请存储权限,走沙盒路径就行。申请权限时我尽量做到最小化,不影响功能的前提下少弹窗,隐私合规压力也小。
隐私合规是上架审核的重点。勤工俭学App会收集学生的学号、姓名、手机号,这些都属于个人信息。我在用户注册时增加了独立的隐私协议勾选,协议里明确列出了收集字段、使用目的、存储期限,以及用户的可删除权利。客户端在首次启动时会弹隐私协议弹窗,用户不同意就无法进入App。服务端也做了对应的日志脱敏,手机号在后台日志里只显示前三位和后四位,避免敏感信息泄露。
备案和软著这块建议提前准备。我在项目功能开发到一半的时候就开始整理软件著作权申请材料,上架前拿到证书,如果要上应用市场,这个几乎是硬性条件。材料整理不复杂,但流程周期长,早点启动不耽误进度。
6.3 后续扩展思路
项目上线后,我留了几个扩展方向的接口。最想做的是岗位智能推荐,根据学生的可工作时间标签和年级课程信息,在首页推荐一批匹配岗位。现有数据模型已经支持了学生的时间标签和岗位的时长要求,后端只需要加一个推荐服务,前端首页加一个推荐模块的列表项即可。
另一个方向是消息推送。勤工俭学的申请审核结果、录用通知都依赖学生主动打开App查看,容易错过时效。接一个推送服务可以大幅提升触达率。OpenHarmony的推送通道和安卓不同,需要接入对应厂商的推送SDK,这块我打算在下个版本做。
数据统计模块也有价值。用工部门关心岗位的报名转化率,学生处想了解各学院参与勤工俭学的活跃度。这个需要后端增加埋点上报和报表接口,前端做几个数据看板页面。现有的数据模型已经能支撑这些查询,只是需要补充统计相关字段。
最后再分享一点个人体会。拿Flutter和OpenHarmony做组合开发,纯Flutter侧的技术难度其实没有想象中高,真正的挑战在于数据模型和页面联动的设计。如果一开始就把岗位状态、申请状态的流转想清楚,页面构建就是搬砖活。反过来,状态没理清就开始堆Widget,后面每改一个功能都要连带改三五个页面,人很容易崩。另外建议所有跨端项目都保留一份平台差异清单,遇到一次记录一次,这份文档在项目后期比很多功能代码都值钱。