# Flutter Schema 驱动表单卡片 ## 目标 Flutter 页面不为每种 OA 表单重复编写固定控件。服务端返回版本化的 JSON Schema 和 UI Schema,客户端从受控组件目录中自动生成移动表单卡片。 ```text 表单定义 API ├─ JSON Schema:字段、类型、必填、枚举和数据约束 └─ UI Schema:分组、顺序、标签、控件和移动端提示 ↓ Flutter Schema 解析 ↓ 白名单控件渲染器 ↓ Riverpod 草稿状态与本地校验 ↓ 用户确认卡片 ↓ 后端再次校验并执行写操作 ``` ## 已实现控件 - `text`:单行文本 - `textArea`:多行文本 - `select`:受控枚举选择 - `dateTime`:日期和时间选择 未知字段或未知控件不能执行动态代码,只显示安全错误提示。后续控件必须通过客户端版本发布进入白名单,例如人员选择、部门选择、附件、金额、定位和明细表格。 ## AI 边界 AI 可以根据自然语言生成表单字段建议值,但不能: - 生成或执行 Dart、JavaScript 等客户端代码。 - 绕过 Schema 枚举和校验规则。 - 绕过用户确认直接提交。 - 自行增加后端未定义的字段。 - 修改隐藏的租户、申请人、审批人或权限字段。 客户端校验用于交互体验,Kotlin 后端仍必须重新执行权限、数据、状态、幂等和业务规则校验。 ## 当前演示 工作台点击“发起请假”后,请假类型、开始时间、结束时间和原因均由 Schema 渲染。点击“AI 自动填写”只更新草稿状态,点击“检查并确认”后才展示最终确认卡片。 客户端优先从受保护的 `/api/v1/form-definitions/leave-request` 加载最新定义,成功后写入本地缓存。网络或认证暂不可用时优先读取最近一次有效缓存;首次离线则使用随客户端发布并经过测试的安全内置定义。运行时可通过 `--dart-define=AIOA_API_BASE_URL=...` 和 `--dart-define=AIOA_ACCESS_TOKEN=...` 配置 API 与开发令牌。 未完成的表单值会在每次字段修改或应用 AI 建议后自动写入本机。再次进入页面时自动恢复并显示来源提示;损坏或不兼容的本地草稿会被隔离清除,避免阻断表单页面。用户可显式清除草稿,后续真实提交成功后也必须清除本地副本。 用户在确认卡片中确认后,客户端调用后端草稿创建 API。请求发出前会持久化请求体与 `Idempotency-Key`;断网或服务端暂时不可用时保留待重试记录,同一份表单再次提交会复用原幂等键,后端成功响应后才清除待重试记录和本地表单草稿。 附件内容不进入 PostgreSQL 或 Flowable 变量。后端为本人草稿创建 MinIO 短期预签名上传任务,客户端直传后通知后端完成;后端重新校验对象大小和 MIME 类型,再将元数据状态改为 `READY`。下载每次重新鉴权并生成短期 URL,上传、完成、下载和删除均写入审计记录。 请假提交成功后为部门主管生成站内待办通知;流程最终批准或驳回后为申请人生成结果通知。Flutter“待办”页包含通知列表和未读数量,点击未读通知后调用后端标记已读。推送仅作为后续提醒通道,权威通知内容与已读状态保存在 PostgreSQL。 主管在 Flutter 待办卡片中查看请假类型、时间与原因,并输入审批意见后批准或驳回。审批请求在本机持久化请求体和幂等键;弱网重试复用同一 `Idempotency-Key`,成功后从列表移除当前任务。 员工可从工作台进入“我的请假申请”,查看草稿和历史申请。草稿可以提交到 Flowable,待审批申请可以撤回;详情页展示权威状态和审批事件时间线。提交与撤回均持久化幂等键,弱网重试不会重复启动或取消流程。 自然语言草稿使用千问 `qwen-plus`:Flutter 只调用受保护的 Kotlin API,Kotlin 记录模型名、提示长度和澄清项数量后调用 Python AI 服务,Python 才持有供应商密钥。模型输出必须通过字段白名单、请假类型枚举、ISO-8601 时区、时间范围和文本长度校验;建议只填入本地草稿,仍需用户确认并由 Kotlin 业务接口重新校验。 Flutter 使用 Keycloak Authorization Code + PKCE 登录。Access Token、Refresh Token 和 ID Token 保存在系统安全存储中,访问令牌在过期前自动刷新;统一 HTTP 客户端只向 Kotlin API Origin 注入 Bearer Token,不会把令牌发送给 MinIO 预签名地址或千问。退出时调用 OIDC End Session 并清除本机凭据。