feat: add Flutter mobile clients and staff delivery API

This commit is contained in:
david
2026-07-30 21:47:41 +08:00
parent 550efb3812
commit 36a5ced1c0
228 changed files with 17159 additions and 22 deletions

View File

@@ -2,7 +2,7 @@
## 1. 产品入口与导航
Flutter App 使用底部导航:智能瓶阀控制、商城、收藏、订单、我的。消息中心作为“我的记录”和通知入口提供,不单独占用底部导航。未登录用户可浏览受限内容;涉及设备、订单、钱包、押金和地址时必须完成登录。
首期 Flutter App 使用“首页、商城、订单、我的”四栏底部导航:首页承载安全内容、公告和服务归属,避免把尚无 Client API 的设备控制与收藏伪装成可用主入口。智能瓶阀控制和收藏在对应服务端契约落地后再进入导航。未登录用户可浏览公开内容与商品;涉及订单、钱包、合同、工单和地址时必须完成登录。
产品设计稿的默认登录页使用“手机号 + 验证码”方式,支持记住登录状态和忘记密码入口;用户名密码登录可作为兼容能力保留。验证码登录应具备频控、图形/行为校验和设备风控;登录前必须展示用户协议和隐私政策,并记录用户同意的协议版本。
@@ -98,3 +98,80 @@ Flutter App 使用底部导航:智能瓶阀控制、商城、收藏、订单
- 充值先创建待支付订单;仅开发配置允许 Mock 支付确认,确认后才写余额及不可变流水。微信和支付宝未配置渠道时必须明确返回不可用,不得模拟成功。
- 商城订单交易状态与物流状态分离;物流单号、公司、发货和收货时间由服务端保存,用户只能查看本人订单并确认收货。
- 首期不伪造设备控制、安全事件、押金、消息、发票、收藏、紧急联系人、账户注销和完整售后能力;文档中这些能力保留为后续迭代,不得以静态成功响应冒充已实现。
## 7. Flutter 开发说明
### 7.1 平台、工程与原型边界
- 用户端只交付 Android、iOS不建设 Flutter Web、桌面端或小程序兼容层。平台差异通过适配器隔离不在业务页面散落 `Platform.isAndroid``Platform.isIOS` 判断。
- 生产工程按规划放在 `apps/user_app`;当前 `ui` 目录是基于产品设计图制作的交互原型,仅用于视觉、信息架构和流程确认,不得把其中的演示数据或模拟成功状态当作业务实现。
- Flutter 与 Dart 版本由工程根目录的版本管理文件和 CI 固定;升级 SDK、Gradle、Kotlin、Xcode、CocoaPods 或插件时必须单独验证 Android/iOS 构建、权限和深链。
- 应用令牌的 client claim 固定为 `user_app`API 根路径固定为 `/heqi/client/v1/user`;不得复用 `service_app` 或任何管理后台会话。
### 7.2 分层结构与依赖方向
采用“按功能组织 UI、按类型组织 Data/Domain”的 MVVM + Repository 结构:
```text
apps/user_app/lib/
app/
app.dart # MaterialApp.router、主题、语言
router.dart # go_router、鉴权与协议确认守卫
dependencies.dart # Service/Repository/ViewModel 装配
data/
models/ # API DTO不直接进入 Widget
services/ # HTTP、扫码、蓝牙、推送、受控存储适配
repositories/ # 缓存、重试、DTO 到领域模型转换
domain/
models/ # 不可变领域模型
use_cases/ # 控阀、下单、余额支付等复杂规则编排
ui/
core/ # 主题、字体、间距、通用状态与组件
features/
auth/
home/
shop/
order/
wallet/
profile/
```
- View 只负责渲染、动画、无障碍语义和导航,不直接发 HTTP、写缓存或决定业务状态。
- ViewModel 暴露不可变 UI state 和明确命令Repository 是远端与本地数据的单一事实入口;跨 Repository 或高风险流程才抽取 Use Case。
- Service 必须无业务状态,负责封装 HTTP、扫码、蓝牙、推送、相机和安全存储等外部边界平台插件通过接口注入便于 Android/iOS 替换与测试。
- DTO、领域模型、UI state 分离。HTTP、日志、深链和 Flutter 页面统一使用 `identity`,不得暴露或接受数据库自增 `id`
### 7.3 路由与导航
- 使用 `MaterialApp.router``go_router`。首期底部四栏使用 `StatefulShellRoute.indexedStack` 保持各分支的滚动位置和页面栈:
- `/home`:安全内容、公告与当前服务归属
- `/shop``/shop/products/:identity``/cart``/checkout`
- `/orders``/orders/:identity``/orders/:identity/delivery`
- `/me``/me/wallet``/me/records``/me/settings`
- `/valves``/favorites` 属于后续路由;对应 Client API 未落地前不得注册可操作页面或用 Mock 数据占据主导航。
- 登录、协议版本确认和首次安全宣导使用根级守卫;涉及设备、订单、钱包、地址的页面必须在 redirect 中校验会话,不能依靠按钮隐藏。
- 邀请二维码、订单通知、支付结果和安全通知使用白名单深链。Android App Links 与 iOS Universal Links 均须校验域名归属;深链参数只接受 `identity` 和短期签名上下文。
- 高风险开阀被拦截时导航到可解释的限制页面或安全事件详情,不允许通过返回栈、群控入口或手工深链绕过。
### 7.4 状态、请求与错误处理
- 所有异步页面统一使用 `initial/loading/content/empty/error/refreshing` 状态;写操作另有 `submitting/succeeded/failed/conflict`,禁止用一个全局 `isLoading` 遮蔽不同请求。
- 设备命令 UI 至少表示待发送、已发送、设备已确认、执行失败、超时待确认、已撤销。创建命令成功后按命令 `identity` 查询回执;超时只能显示“待确认”,不能回退为“已成功”。
- 写请求生成并持久化幂等键;重试复用原键。余额支付、充值、提现、订单提交、报修和设备命令均不得因页面重建或网络重连重复创建事实。
- ViewModel 根据稳定错误码映射可操作文案与恢复入口不得解析后端错误文案驱动流程。401/403、状态冲突、限流、外部依赖不可用和未知错误分别处理。
- 金额以最小货币单位整数进入领域层,只在格式化组件中转换为展示文本;时间统一解析为 UTC 并按用户时区展示,同时保留数据更新时间。
### 7.5 本地数据、安全与平台能力
- 访问令牌、刷新令牌和支付相关临时凭据只进入 Android Keystore / iOS Keychain 支持的安全存储;日志、崩溃报告、埋点和剪贴板不得记录令牌、支付密码、完整手机号、地址或定位。
- 普通缓存只保存可恢复数据并设置版本与过期时间。安全事件、资金、订单和设备命令的服务端事实不能由本地缓存覆盖;退出登录时按数据分类清理。
- Android/iOS 的相机、相册、蓝牙、定位、通知权限均采用使用时申请和拒绝后降级。定位失败提供手动地址入口;蓝牙失败提供扫码或手动设备码入口。
- 推送点击必须先恢复会话并重新向服务端读取对象状态;通知载荷不得包含完整地址、手机号、支付信息或可直接执行设备控制的凭证。
- 屏幕截图、应用切后台和最近任务缩略图对支付密码、银行卡、证件等页面按风险实施遮挡;是否禁止安全事件页面截图由合规评审决定。
### 7.6 视觉、无障碍与测试
- 设计基线以 `doc/用户端APP-产品设计``ui/?app=user` 为准:安全蓝为主色,瓶阀状态、告警与命令回执优先于营销内容。危险、警告、成功不能只靠颜色表达。
- 使用统一 ThemeExtension 管理颜色、圆角、间距、阴影和状态色正文最小字号、动态字体缩放、44×44 logical pixels 触控目标、屏幕阅读器语义和对比度必须在 Android/iOS 真机验证。
- ViewModel、Use Case、Repository 覆盖单元测试;瓶阀状态、支付、登录守卫和错误恢复覆盖 Widget 测试;扫码绑定、开关阀回执、下单支付、订单轨迹覆盖集成测试。
- 每次合并至少执行 `flutter analyze``flutter test`、Android debug 构建和 iOS Simulator 构建涉及相机、蓝牙、推送、支付、Universal Links/App Links 的改动还须执行对应真机回归。

View File

@@ -2,7 +2,7 @@
## 1. 定位与角色模型
服务端 App 是安装维修员、安检员和配送员共用的 Flutter 移动应用。“服务端”在本文指服务人员端,并非后端服务。用户登录后由平台分配角色、资质所属可燃气体站/配送点和服务区域;具有多个角色时可切换工作台,但每次操作都带角色和组织上下文。
服务端 App 是安装维修员、安检员和配送员共用的 Flutter 移动应用。“服务端”在本文指服务人员端,并非后端服务。首期采用“一账号一角色”:用户登录后由平台返回唯一岗位、资质所属站/配送点,客户端不得自行切换或拼装角色上下文。
设计稿要求不同角色使用同一登录与账户体系,但加载不同的底部导航和工作台:配送员使用“订单、任务、用户、我的”,安装维修员使用“工单、巡检、记录、我的”,安检员使用“任务、记录、隐患、我的”。导航、数据和接口均按当前角色、组织、服务区域与资质过滤。
@@ -118,8 +118,107 @@
## 8. 首期 Client API 落地边界2026-07
- 工作人员 API 固定为 `/heqi/client/v1/staff`,令牌客户端为 `service_app`;不提供注册,只允许后台已创建、启用且岗位受支持的账户登录。
- 三类岗位均属于同一 Staff Client API安装维修和安检使用 `/tickets` 工单接口;配送使用 `/delivery/orders`、开始配送、轨迹批量补传、到达围栏校验、异常/恢复和提交签收接口。所有查询和动作只允许访问当前账号被分派的对象。
- 登录后调用 `/preflight`。首期真实校验账号、唯一岗位、所属组织、资质有效期和上班状态;每日培训、服务区域与授权设备尚未配置时返回 `not_configured`,客户端必须明确展示,不能显示为“已通过”。
- 首期岗位为配送、安装维修、安检。安装/维修工单只分派给安装维修人员,安检/复检只分派给安检人员,客服类工单不进入工作人员 App。
- 工单统一复用 `cs_ticket`,状态为待分派、已分派、处理中、异常、待用户确认、已完成或已取消。工作人员只能操作分派给本人的工单;现场结果必须包含定位、原始采集时间、上传资源地址和幂等号。
- 安装和维修至少提交前、中、后图片及用户签名;安检和复检至少提交一张图片、结果及用户签名。单次最多六张图片、三段视频。不合规或高风险结论只能进入异常,不能提交待用户确认。
- 配送人员只操作分派给本人的供气配送订单,可开始配送、批量补传轨迹、到达校验、异常/恢复和提交签收;不能修改订单金额。到达以订单地址坐标和配置地理围栏为准。
- 工作人员钱包与用户钱包复用统一模型,支持余额、充值订单、不可变流水、提现及银行卡;服务收入只能由已完成业务事实产生,客户端不能直接增加余额。
## 9. Flutter 开发说明
### 9.1 平台、角色与工程边界
- 服务人员端只交付 Android、iOS。生产工程按规划放在 `apps/service_app`;当前 `ui` 目录是配送、安装维修、安检三类原型的交互实现,不连接真实定位、相机、蓝牙、支付或业务 API。
- 三类岗位共用一个 Flutter 应用和登录体系,每个账号仅有一个服务岗位,令牌 client claim 固定为 `service_app` 并携带唯一 `role_code`。岗位变更必须由后台完成并重新登录换取令牌;客户端不提供角色切换。
- 工作人员 API 根路径固定为 `/heqi/client/v1/staff`。客户端不得访问用户端、平台后台、气站后台或配送点后台的令牌与接口。
- 首期后端不提供工作人员自助注册时Flutter 注册页面不得伪造成功;若未来开放申请,只能创建待审核账户,不能直接授予岗位能力。
### 9.2 分层结构与角色化功能
采用 MVVM + RepositoryUI、业务编排和数据边界严格分离
```text
apps/service_app/lib/
app/
app.dart
router.dart # go_router、岗位与作业前置守卫
role_context.dart # 当前唯一岗位、组织和服务区域
dependencies.dart
data/
models/
services/ # API、定位、相机、蓝牙、上传、安全存储
repositories/ # 任务、取证、轨迹、钱包、离线队列
offline/ # 加密草稿、Outbox、冲突与补传
domain/
models/
use_cases/ # 到场、扫描、安检、安装、完成任务
ui/
core/ # 角色主题、通用状态、取证组件
features/
auth/
workbench/
delivery/
installation/
inspection/
evidence/
hazard/
records/
wallet/
profile/
```
- 共用任务卡、步骤器、相机、签名、弱网提示等展示组件;配送、安装维修、安检分别拥有独立 ViewModel、Use Case、检查表解释器和状态机不得用一个万能表单加前端条件判断代替领域规则。
- View 不直接更新任务状态、金额、风险等级或本地数据库。ViewModel 只发起命令并呈现服务端返回状态Repository 负责 API、本地加密草稿、上传队列和领域模型转换。
- 检查表、强制取证项、材料清单和步骤顺序由服务端版本化下发。客户端缓存模板版本,提交时带模板 `identity` 和版本;过期模板必须进入冲突处理,不能静默套用新模板。
- 所有任务、证据、轨迹、人员、组织和钱包引用只使用 `identity`;数据库内部自增 ID 不进入 Flutter 模型、日志、深链或离线队列。
### 9.3 路由、底部导航与守卫
- 使用 `MaterialApp.router``go_router`。登录后先进入 `/preflight`,统一校验账户启用、岗位、组织、资质、每日培训、上班状态、服务区域和授权设备,再进入角色工作台。
- 首期角色底部导航使用 `StatefulShellRoute.indexedStack`,只暴露有真实 Client API 的入口:
- 配送员:`/work`(本人配送订单)、`/records`(已完成记录)、`/me`
- 安装维修员与安检员:`/work`(本人工单)、`/records`(已完成记录)、`/me`
- 任务详情统一使用 `/tasks/:identity`,具体步骤使用 `/tasks/:identity/steps/:stepCode`;路由解析后仍须从服务端读取任务类型、当前状态和允许动作,不能信任路径中的角色或步骤。
- 未完成培训、未上班、资质失效、超出服务区或任务未分派给本人时,守卫导航到明确的阻断页;返回栈、通知深链和手工 URL 均不能绕过。
- 普通退出时若存在未提交草稿,必须先返回上传或二次确认放弃并安全删除;令牌失效或异常退出时按账号加密封存,只有同一账号重新认证后可恢复,换账号不可见。
### 9.4 任务状态与现场作业
- 每类任务由独立状态模型驱动UI 只展示服务端返回的 `allowed_actions`。按钮禁用、步骤条位置和本地完成标记不构成业务校验。
- 配送流程至少实现订单确认、导航到场、围栏校验、气瓶扫描、随瓶安检、收款确认、签收/回收;安装流程按使用条件、备料、安装、测试、前期安检、用户确认、收款执行;安检流程按详情、执行、检查、风险等级、签名、隐患/复检执行。
- 不合格、高风险、测试失败、围栏异常、金额差异、证据缺失和设备回执不确定时只能进入异常、待整改或待确认,不能由客户端跳到已完成。
- 关阀、设备激活、任务完结、收款和提现均显示服务端确认状态。请求成功、文件进入上传队列或本地步骤完成不得显示为最终成功。
- 检查项使用稳定代码;照片类型、材料、风险等级和错误原因由契约映射,禁止根据中文标题或错误文案驱动状态流转。
### 9.5 离线队列、定位与证据
- 离线能力只覆盖已分配任务的只读信息和现场草稿。关阀、激活、资金、最终完结等动作必须在线确认;离线时明确告知“已暂存/待补传”,不能显示“已完成”。
- 本地使用平台安全存储保护数据库密钥,任务草稿、轨迹点、照片/视频元数据、签名、扫描结果和收款确认采用加密数据库或加密文件。退出角色或账户时按服务端留存策略处理,不能只删索引留下明文文件。
- 每个离线写入包含本地操作 `identity`、业务对象 `identity`、幂等键、原始采集时间、来源、完整性标记和内容哈希。补传保留原始时间按依赖顺序投递409/版本冲突进入人工可见的冲突队列。
- 文件先落加密暂存区并记录哈希,获得短期上传授权后上传;业务提交只引用上传成功的资源 URI。失败重试不得重复创建证据后台删除或拒绝的附件不得被本地队列重新“复活”。
- 配送定位仅在履约期间、满足权限与任务状态时采集。Android 前台服务和 iOS 后台定位必须显示系统要求的可见提示;权限撤销、精度不足、后台受限和长时间无点位均写入任务状态。
- 用户签名画布保存矢量笔画或可验证位图及哈希,并与结论、任务、操作者和采集时间绑定;禁止复用其他任务签名。
### 9.6 Android/iOS 平台适配
| 能力 | Android | iOS |
| --- | --- | --- |
| 相机/相册 | 运行时按用途申请 Camera/Photo Picker使用系统选择器优先 | 使用相机和 Photos 限定访问,解释用途并处理 Limited 状态 |
| 蓝牙扫描 | 按系统版本申请 Nearby Devices/Bluetooth 权限 | 配置 Bluetooth 用途说明,仅在任务步骤内扫描 |
| 定位 | 前台精确定位;配送后台轨迹使用合规前台服务与常驻通知 | 先申请 When In Use确需配送后台定位时再升级并配置 Background Modes |
| 通知 | 创建安全、任务、上传三类通知渠道,高风险安全通知不可静默关闭 | 分类注册通知操作,点击后重新读取任务和权限 |
| 文件暂存 | App 私有目录、加密数据库,禁止写公共目录 | Application Support/Library 私有目录,排除不必要云备份 |
| 深链 | App Links 校验域名与签名证书 | Universal Links 配置 Associated Domains |
- 权限说明必须与实际采集行为一致。拒绝权限时给出可恢复路径;不得因相机或精确定位权限失败伪造照片、地址或到场结果。
- 地图、导航、蓝牙、相机和签名通过抽象 Service 注入Android/iOS 实现返回统一领域结果和稳定错误码。
### 9.7 视觉、性能与测试
- 设计基线以 `doc/服务端APP-配送端-产品设计``doc/服务端APP-安装端-产品设计``doc/服务端APP-安全检查端-产品设计``ui/?app=service&role=...` 为准。统一使用紫色作业框架,安检关键成功动作可使用安全绿色;风险等级必须同时展示文字、图标和颜色。
- 任务详情优先展示任务号、类型、预约/SLA、地址、脱敏联系人、风险与允许动作。固定底部操作按钮不得遮挡检查项、签名或系统安全区。
- 长清单使用惰性列表和分段保存;照片视频缩略图解码、压缩和上传移出 UI isolate。后台轨迹、上传和补传必须受电量、网络与系统调度约束不用常驻无限循环。
- ViewModel、Use Case、Repository、离线队列和冲突处理覆盖单元测试角色/组织守卫、步骤前置、风险结论和弱网状态覆盖 Widget 测试;三角色主闭环覆盖 Android/iOS 集成测试。
- 每次合并至少执行 `flutter analyze``flutter test`、Android debug 构建和 iOS Simulator 构建定位、相机、蓝牙、推送、后台任务、App Links/Universal Links 和安全存储改动必须真机回归。

View File

@@ -13,8 +13,8 @@
| 层级 | 推荐技术 | 用途 |
| --- | --- | --- |
| 用户端 App | Flutter | 用户设备、安全、商城、订单、钱包、消息和个人中心 |
| 服务端 App | Flutter | 安装维修、安检、配送三类工作台;通过角色与能力包控制模块 |
| 用户端 App | Flutter 3 / Dart 3 | 首期首页内容、服务归属、商城、订单、合同、工单、钱包、地址和个人中心 |
| 服务端 App | Flutter 3 / Dart 3 | 配送、安装维修、安检三类单角色账号工作台、现场取证与受控离线草稿 |
| 平台总后台 | Vue 3 + TypeScript | 全局治理、运营、财务、安全、审计等高密度管理页面 |
| 可燃气体站管理系统 | Vue 3 + TypeScript | 站点商品、订单、服务、库存与经营管理 |
| 配送点管理系统 | Vue 3 + TypeScript | 调度、配送仓、路线、人员和配送运营工作台 |
@@ -72,6 +72,13 @@ flowchart LR
- 已分配任务、轨迹点、照片/视频元数据、签名、扫描结果和收款确认可在弱网下加密暂存。补传必须携带原始采集时间、服务端接收时间、任务 `identity`、操作者 `identity`、来源、完整性标记与幂等键,禁止以补传时间覆盖采集时间。
- 后端负责乱序校正、重复去除、异常速度/精度标记、证据哈希与状态机校验;前端离线缓存、按钮禁用或页面显示不能替代服务端权限、金额、地理围栏和完成条件校验。
### 4.2 Flutter 工程落地基线
- 生产工程已落在 `apps/user_app``apps/service_app`,只生成 Android、iOS 平台目录;`ui` 继续作为视觉和交互原型,不作为运行时依赖。
- 两个 App 使用 `MaterialApp.router``go_router`、MVVM + Repository 与注入的平台 Service。HTTP 根地址通过 `--dart-define=API_BASE_URL=...` 注入;用户端和工作人员端分别固定访问 `/heqi/client/v1/user``/heqi/client/v1/staff`JWT 请求头沿用当前服务端原始令牌契约。
- 访问令牌保存在 Android Keystore / iOS Keychain 支持的安全存储。服务端 App 的现场草稿和附件按账号使用 AES-GCM 加密;恢复网络后才上传并执行最终业务提交。
- 充值 Mock 确认只允许 Debug/开发联调Release UI 不注册该入口;未落地的设备控制、收藏、押金、消息和发票能力不得以静态成功状态替代。
## 5. 研发目录规划(建议)
```text
@@ -105,7 +112,7 @@ platforms/
performance/ # 遥测、订单、轨迹与消息积压压测
```
该结构是后续开发建议,不代表本次创建了任何代码目录或代码文件
其中 `apps/user_app``apps/service_app``backend/{api,worker,iot}` 与当前管理端目录已经落地;其余标记为规划的目录仍不得因局部任务提前创建空壳
## 6. 后端领域划分
@@ -143,8 +150,8 @@ platforms/
| 开放接口 | `api_` | `api_product``api_client``api_subscription` |
| 平台任务 | `sys_` | `sys_outbox_event``sys_dead_letter_event` |
- 所有主表必须包含 `identity` 字段,类型为 UUID V7并作为该表的主键。UUID V7 由应用服务生成,保证时间有序性;禁止使用数据库自增主键、随机 UUID V4 或将业务编号作为主键
- 引用主表时,外键字段命名为 `<实体>_identity`,例如 `order_identity``service_person_identity`。业务展示编号(订单号、设备编码、站点编码等)应使用独立字段并设置唯一约束,不能替代 `identity`
- 每张表必须包含数据库内部使用的 `id bigint` 自增主键;主表还必须包含由应用生成、带唯一索引的 UUID V7 `identity varchar(36)`。HTTP、消息、审计日志、Flutter/Vue 模型和跨服务引用只使用 `identity`,不得暴露或接受内部 `id`
- 数据库内部关联优先使用 `<实体词根>_id` 指向自增主键;跨服务契约、异步事件和审计关联使用 `<实体词根>_identity`。业务展示编号另设唯一字段,不能替代 `id` `identity`
- 每个主表还应按需要包含 `created_at``updated_at``created_by_identity``updated_by_identity``status``version` 等审计/并发字段;资金流水、安全事件、审计日志等不可变记录不得被物理删除。
- 数据库表、字段、索引、约束和枚举必须编写中文注释;注释说明业务含义、取值/单位、脱敏或留存要求。模型注释与接口契约必须同步维护,禁止只在设计文档中说明。