chengc 21 hours ago
parent
commit
f7bf6f2569

File diff suppressed because it is too large
+ 0 - 1668
docs/superpowers/plans/2026-05-29-tboss-oa-implementation.md


File diff suppressed because it is too large
+ 0 - 2071
docs/superpowers/specs/2026-05-30-tboss-oa-database.md


File diff suppressed because it is too large
+ 0 - 1052
docs/superpowers/specs/2026-05-30-tboss-oa-design.md


+ 0 - 509
docs/superpowers/specs/2026-05-30-tboss-oa-interactions.md

@@ -1,509 +0,0 @@
-# TBOSS OA 模块 — 全页面元素操作事件与跳转关系说明
-
-> 基于 `2026-05-30-tboss-oa-design.md` 和 `2026-05-30-tboss-oa-database.md`,覆盖 27 个页面的所有可交互元素及其操作逻辑。
-
----
-
-## 页面跳转关系总图
-
-```
-/ (Appshell)
-├─ /messages ──────────────────────────────────────────────────────────────
-│   └─ (点击消息卡片) → /announcement/detail/:id
-│                    → /expense-apply/detail/:id
-│                    → /expense/detail/:id
-│                    → /overtime/detail/:id
-│                    → /vehicle/detail/:id
-│
-├─ /home ──────────────────────────────────────────────────────────────────
-│   ├─ (轮播图) → 绑定的公告/活动链接 或 全屏预览
-│   ├─ (金刚区-发起) → /expense-apply/apply    /expense/apply
-│   │                → /vehicle/apply           /overtime/apply
-│   ├─ (金刚区-记录) → /expense-apply/list      /expense/list
-│   │                → /outing-log/list         /announcement/list
-│   ├─ (看板-已提)   → /expense-apply/list
-│   ├─ (看板-报销)   → /report/expense-detail
-│   └─ (看板-待处理) → /messages
-│
-└─ /profile ───────────────────────────────────────────────────────────────
-    ├─ 审批历史 → (子页面) → (点击单据) → /expense-apply/detail/:id
-    │                                   → /expense/detail/:id
-    │                                   → /overtime/detail/:id
-    │                                   → /vehicle/detail/:id
-    ├─ 我的报表 → (子页面) → /report/.. (5 Tab)
-    └─ 关于     → (子页面)
-
-表单页 (apply) ────(存为草稿/提交)──→ 列表页 (list) ──(点击卡片)──→ 详情页 (detail/:id)
-    ↑                                    │                        │
-    └───────(左滑编辑/draft编辑)─────────┘    (rejected→重新编辑)──┘
-
-/admin/permissions  (仅管理员)
-/announcement/create (仅管理员)
-```
-
----
-
-## 页面 1:Appshell 底部导航外壳页 (`/`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 消息 Tab | 点击 | 路由 → `/messages`,图标变 `#00ABF3` | — |
-| 消息 Tab 角标 `TDBadge` | 数据绑定 | 未读消息数;进入时拉取,之后 30s 轮询或 Push 刷新 | `GET /api/messages/unread-count` |
-| 工作台 Tab | 点击 | 路由 → `/home`,图标变 `#00ABF3` | — |
-| 我的 Tab | 点击 | 路由 → `/profile`,图标变 `#00ABF3` | — |
-| 当前激活 Tab | 重复点击 | 若对应页面含列表 → ScrollToTop;否则无行为 | — |
-
----
-
-## 页面 2:消息通知聚合页 (`/messages`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 导航栏标题 | — | "消息通知" | — |
-| 导航栏"全部已读" | 点击 | 仅未读消息>0 时显示;调用接口,所有红点淡出,按钮消失 | `POST /api/messages/read-all` |
-| 消息卡片(公告类) | 点击 | 路由 → `/announcement/detail/:id` | `BizType='announcement'` |
-| 消息卡片(审批待办) | 点击 | 按 `BizType` 路由 → 对应详情页,底部直接展示审批操作栏 | `BizType` 决定路由 |
-| 消息卡片(审批结果) | 点击 | 按 `BizType` 路由 → 对应详情页 | `BizType` 决定路由 |
-
-| 已读消息卡片 | — | 透明度 0.6,无红点,不可左滑 | `IsRead=1` |
-| 未读消息卡片-左滑 | 手势 | 滑出 `[标记已读]`(蓝色) + `[删除]`(红色) | — |
-| `[标记已读]` | 点击 | 异步标记;成功→红点淡出+透明度↓0.6;失败→Toast 红色"操作失败,请重试" | `PUT /api/messages/:id/read` |
-| `[删除]` | 点击 | `TDDialog`:"确认删除?删除后不可恢复。"→ 确定→卡片收起动画+软删除(IsDeleted=1) | `PUT /api/messages/:id/delete` |
-| 列表区域 | 下拉 | `RefreshIndicator`,重置 `page=1` | `GET /api/messages?page=1` |
-| 列表区域 | 上拉触底 | `page++`,底部菊花+"加载中...",静默追加 | `GET /api/messages?page=N` |
-| 空列表 | — | `TDEmpty` + 铃铛占位图:"暂无消息通知" | — |
-
----
-
-## 页面 3:工作台 (`/home`)
-
-### 员工版
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 导航栏标题 | — | "TBOSS 工作台" | — |
-| `TDRotation` 轮播图 | 自动 | 每 3s 切换,循环 | `SysBanner` (IsActive=1, IsDeleted=0, ORDER BY SortOrder) |
-| 轮播图 | 点击 | 有 `LinkUrl`→跳转;无→全屏预览(双指缩放) | `SysBanner.LinkUrl` |
-| 金刚区-事前申请 | 点击 | 路由 → `/expense-apply/apply` | — |
-| 金刚区-费用报销 | 点击 | 路由 → `/expense/apply` | — |
-| 金刚区-用车申请 | 点击 | 路由 → `/vehicle/apply` | — |
-| 金刚区-加班申请 | 点击 | 路由 → `/overtime/apply` | — |
-| 金刚区-申请记录 | 点击 | 路由 → `/expense-apply/list`(默认"全部"Chip) | — |
-| 金刚区-报销记录 | 点击 | 路由 → `/expense/list`(默认"全部"Chip) | — |
-| 金刚区-外勤日志 | 点击 | 路由 → `/outing-log/list` | — |
-| 金刚区-公司公告 | 点击 | 路由 → `/announcement/list` | — |
-| 快捷看板-已提单据总数 | 点击 | 路由 → `/expense-apply/list`(默认"全部") | 后端预计算(缓存5min) |
-| 快捷看板-本月累计报销 | 点击 | 路由 → `/report/expense-detail` | 后端预计算(缓存5min) |
-| 快捷看板-待处理总数 | 点击 | 路由 → `/messages`(筛选 `approval_notice`) | 后端预计算(缓存5min) |
-| 页面整体 | 下拉 | 强制穿透缓存刷新轮播图+看板数据 | — |
-
-### 经理版增量
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 待审批卡片区 `TDBadge` | — | 红色数字展示待办件数 | `COUNT(ApprovalRecord WHERE ApproverId=当前用户 AND Action='pending' AND IsValid=1)` |
-| 待审批卡片区 | 点击 | 路由 → `/messages`(筛选 `approval_notice`) | — |
-
-### 财务版增量
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 财务看盘卡片 | — | 只读展示:已支付流水/待付款总额/异常退回数 | 全公司聚合查询 |
-
----
-
-## 页面 3.1:我的个人中心页 (`/profile`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 头像 `TDAvatar` | 点击 | 唤起相册/相机 → 裁剪(1:1,≤2MB,JPG/PNG) → 上传 → 全局刷新 | `PUT /api/user/avatar` → `SysUser.AvatarUrl` |
-| 个人信息卡片 | — | 展示真实姓名、部门、岗位 | `SysUser.RealName`, `DeptName`, `Position` |
-| 我的审批历史 | 点击 | 路由 → 审批历史子页面 | — |
-| 我的报表 | 点击 | 路由 → 报表聚合子页面(5 Tab) | — |
-| 关于 TBOSS OA | 点击 | 路由 → 关于子页面 | — |
-| 关于-用户协议 | 点击 | `url_launcher` → 宿主 WebView | 远程 HTML |
-| 关于-隐私政策 | 点击 | `url_launcher` → 宿主 WebView | 远程 HTML |
-
-### 子页面:我的审批历史
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 导航栏标题 | — | "我的审批历史" | — |
-| 单据卡片-编号 | 点击 | 按单据类型跳转详情页 | `ApprovalRecord.BizId` + `BizType` |
-| 列表区域 | 下拉 | 刷新 `page=1` | `ApplicantId` 反查 4 张业务主表 JOIN `ApprovalRecord` |
-| 列表区域 | 上拉触底 | `page++` | 同上 |
-| 空列表 | — | `TDEmpty`:"暂无审批记录" | — |
-
-### 子页面:我的报表聚合页
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| `TDTabBar`(5 Tab) | 切换 | 下方渲染对应页面 21~25 的个人报表视图 |
-
----
-
-## 页面 4:事前申请表单页 (`/expense-apply/apply`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 申请人姓名 | — | 只读 | `SysUser.RealName` |
-| 所属部门 | — | 只读 | `SysUser.DeptId → SysDepartment.DeptName` |
-| 申请日期 | — | 只读,`DateTime.now()`,YYYY-MM-DD | — |
-| 紧急程度 `TDRadioGroup` | 选择 | 普通/紧急/特急,默认"普通" | `ExpenseApplication.Urgency` |
-| 费用类型 `TDPicker` | 选择 | 差旅费/招待费/日常采购/活动经费;联动过滤预算科目候选 | `ExpenseApplication.ExpenseType` |
-| 费用事由 `TDTextarea` | 输入 | 限制 200 字 | `ExpenseApplication.Purpose` |
-| 关联项目 `TDPicker` | 级联选择 | 一级:`SysProject.Name` → 二级:`SysBudgetSubject.Name`(通过 `SysProjectBudget` 过滤) | `ExpenseApplication.ProjectId` |
-| 预算科目 `TDPicker` | 选择 | 选完项目+科目后异步加载可用余额 | `ExpenseApplication.BudgetSubjectId` |
-| 预算余额展示 | — | 只读"¥XXXX.xx" | `SysProjectBudget.AvailableAmount`(ProjectId+SubjectId) |
-| 预估金额 `TDInput` | 输入 | 纯数字键盘;汇总所有明细行,超预算→金额变红+警告文本 | `ExpenseAppDetail.EstimatedAmount` |
-| [+ 添加费用明细] | 点击 | 展开新明细卡片,页面滚动聚焦 | — |
-| 明细卡片 ✕ | 点击 | 气泡"确认删除"→ 折叠消失 | — |
-| 附件上传 [+] | 点击 | 唤起相册/相机/文件;≤10MB(图)/≤20MB(PDF);超限 Toast | `ExpenseApplicationAttachment` |
-| 附件缩略图 | 点击 | 黑底全屏预览,双指缩放(0.5x~3x),左右滑动,右上角关闭 | — |
-| 附件缩略图 ⊖ | 点击 | 气泡"删除该附件?"→ 缩小消失+删除服务器文件 | — |
-| PDF 附件 | 点击 | 原生 PDF 查看器打开 | — |
-| [重置] | 点击 | 仅编辑草稿时可见;确认弹窗→清空至初始状态 | — |
-| [存为草稿] | 点击 | Loading → `PUT /expense-apply/draft` → Toast 绿色 → 跳转列表(激活"草稿"Chip);失败→Toast 红色 | `ExpenseApplication` (Status='draft') |
-| [提交审批] | 点击 | 校验→ScrollTo 报错字段(300ms)+边框红闪+`TDMessage`;通过→Loading→`POST /expense-apply/submit`→Toast→跳转列表 | `ExpenseApplication` (Status='pending') |
-| 返回按钮(有未保存修改) | 点击 | `TDDialog`:"是否退出?"→ 继续编辑/放弃并退出 | — |
-
----
-
-## 页面 5:费用报销表单页 (`/expense/apply`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| [一键导入] 快捷链 | 点击 | 半屏抽屉:`approved` 且未被引用的申请列表;空→`TDEmpty`;选一条覆盖回填 | `Expense.SourceApplicationId` 排除已引用 |
-| 报销事由 | 输入 | 可由导入回填或手工填写 | `Expense.Purpose` |
-| 关联项目 | 选择 | 草稿可空 | `Expense.ProjectId` |
-| 预算科目 | 选择 | 草稿可空 | `Expense.BudgetSubjectId` |
-| 关联成本中心 `TDPicker` | 选择 | 可空,选项来自 `SysCostCenter` | `Expense.CostCenterId` |
-| 报销总金额 | — | 只读,明细累加上浮动画 | `Expense.TotalAmount` |
-| 开户行 `TDInput` | 输入+联想 | `/api/banks` 下拉联想,可自由输入 | `Expense.BankName` |
-| 开户人户名 | 输入 | 默认当前用户姓名,可修改 | `Expense.AccountName` |
-| 银行账号 `TDInput` | 输入 | 前端校验 16-19 位;提交时后端再校验;首次提交后回写默认账户 | `Expense.BankAccount` → `SysUser.DefaultBankAccount` |
-| 发生日期 | 点击 | `TDDatePicker`(年月日,无时分) | `ExpenseDetail.ExpenseDate` |
-| 费用类别 `TDPicker` | 选择 | 选项来自 `SysCostCategory`(叶子节点) | `ExpenseDetail.ExpenseType` |
-| 发票金额 `TDInput` | 输入 | 纯数字键盘 | `ExpenseDetail.TotalAmount` |
-| [+ 添加费用明细] | 点击 | 平滑展开新明细卡片 | — |
-| 明细行 ✕ | 点击 | 气泡确认→折叠消失 | — |
-| 发票上传 [+] | 点击 | 相册/相机,≤10MB;满 9 隐藏 | `ExpenseAttachment` |
-| 发票缩略图 | 点击 | 黑底全屏预览 | — |
-| [存为草稿] | 点击 | 复用页面 4 逻辑 | — |
-| [提交审批] | 点击 | 复用页面 4 逻辑 | — |
-| 返回按钮(有未保存修改) | 点击 | 复用页面 4 逻辑 | — |
-
----
-
-## 页面 6:事前申请列表页 (`/expense-apply/list`)
-
-## 页面 7:费用报销列表页 (`/expense/list`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 导航栏标题 | — | "申请记录" / "报销记录" | — |
-| `TDChip`(全部/草稿/审批中/已通过/已拒绝/已撤回) | 点击 | 高亮 `#00ABF3`,列表淡入淡出刷新;无数据→`TDEmpty`(文案随 Chip 变化) | `ExpenseApplication.Status` / `Expense.Status` |
-| 列表区域 | 下拉 | 刷新,`page=1` | — |
-| 列表区域 | 上拉触底 | `page++`,追加卡片 | — |
-| 卡片空白区 | 点击 | 路由 → 详情页 | `/expense-apply/detail/:id` 或 `/expense/detail/:id` |
-| 卡片左滑-`[编辑]` | 点击 | 仅 draft/rejected;路由 → 表单编辑态 | `/expense-apply/apply?id=xxx` 或 `/expense/apply?id=xxx` |
-| 卡片左滑-`[删除]` | 点击 | 仅 draft/rejected;`TDDialog`"确认删除?不可恢复。"→ 卡片收起+软删除 | — |
-| 卡片左滑(其他状态) | — | 无反应 | — |
-| `StatusTag` | — | 仅展示(灰-草稿/橙-审批中/绿-已通过/红-已拒绝/灰-已撤回) | — |
-
-### 经理版增量
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| 访问范围标签 `[我的发起]` | 点击 | 与员工版逻辑相同 |
-| 访问范围标签 `[下属审批]` | 点击 | 列表清空+骨架屏→加载部门下属单据 |
-| 下属卡片左滑-`[一键同意]` | 点击 | 仅 `pending` 状态;绿色按钮,无需进入详情页即审批通过 |
-
-### 财务版增量(仅页面 7)
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| `[待付款]` Chip | 点击 | 筛选 `Status='approved' AND PaymentStatus='unpaid'` |
-| 卡片左滑-`[线下已付款]` | 点击 | 标记 `PaymentStatus='paid'` |
-
----
-
-## 页面 8:事前申请详情页 (`/expense-apply/detail/:id`)
-
-## 页面 9:费用报销详情页 (`/expense/detail/:id`)
-
-### 员工版
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 状态横幅 | — | 图标+状态中文+当前审批人姓名 | `Status` + `CurrentApproverId → SysUser.RealName` |
-| 提交时间 | — | 只读"YYYY-MM-DD HH:mm" | `CreateTime` |
-| 费用明细表"展开全部" | 点击 | >5行时显示;平滑展开 | `ExpenseAppDetail` / `ExpenseDetail` |
-| 附件缩略图 | 点击 | 黑底全屏预览,双指缩放,左右滑动 | `ExpenseApplicationAttachment` / `ExpenseAttachment` |
-| 时间线节点 | 点击 | 当前用户待审批→弹出审批操作;否则→展开详情(审批人/时间/意见) | `ApprovalRecord` (IsValid=1 ORDER BY ApprovalLevel) |
-| 审批人姓名/头像 | 点击 | `TDDialog` 展示基本信息卡片(姓名+部门+岗位) | `ApproverId → SysUser` |
-| [编辑](draft) | 点击 | 路由 → 表单编辑态 | — |
-| [提交审批](draft) | 点击 | 校验→提交;失败→`TDDialog`"去编辑"引导 | — |
-| [撤回申请](pending) | 点击 | `TDDialog`"确认撤回?"→ 确定→Status='withdrawn'→页面刷新 | — |
-| [重新编辑并发起](rejected) | 点击 | 路由 → 表单编辑态;提交后更新原记录+旧审批链 IsValid=0 | `ApprovalRecord.IsValid` |
-
-### 经理版底部操作栏(仅 pending 状态且当前审批人=该经理时展示)
-
-| 元素 | 事件 | 行为 | 接口 |
-|------|------|------|------|
-| [拒绝] | 点击 | `TDDialog` 输入理由(≥5字解锁确定按钮)→ `POST` | `/api/{bizType}/approve` (action=reject) |
-| [转交] | 点击 | MethodChannel 唤起通讯录单选 → `POST` | `/api/{bizType}/approve` (action=transfer) |
-| [同意] | 点击 | Loading → `POST` → 节点变绿 → 流转 | `/api/{bizType}/approve` (action=approve) |
-
-### 管理员版底部操作栏
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| [强制终止] | 点击 | `TDDialog` 确认 → Status='withdrawn' + 写入终止原因 |
-| [查看审批链] | 点击 | 展开完整 `ApprovalRecord` 历史(含 IsValid=0 的失效记录) |
-
-### 财务版增量(仅页面 9)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 合规复选框 ×3 | 勾选 | 全部勾选→解锁 [确认打款] 按钮 | `Expense.IsInvoiceVerified/IsTaxIdMatched/IsCategoryCompliant` |
-| [退回修改] | 点击 | 选择退回节点(员工/经理)→状态倒流 | — |
-| [确认已线下打款并归档] | 点击 | 解锁后可用;滑出凭证表单(电汇流水号+记账凭证号必填)→提交→`Paid` | `Expense.BankTransferNo` + `VoucherNo` |
-| [下一笔待付款] | 点击 | 归档后替换显示;跳转下一条 `approved+unpaid`;无→"已无待付款单据" | — |
-
----
-
-## 页面 10:加班申请表单页 (`/overtime/apply`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 加班类型 `TDPicker` | 选择 | 工作日/休息日/节假日,默认"工作日";节假日标注费率 | `Overtime.OtType` |
-| 补偿方式 `TDRadio` | 选择 | 转调休/结算加班费/混合,默认"转调休" | `Overtime.CompensationType` |
-| 混合模式 `TDSlider` | 拖动 | 10%~90%,步长 10%;右侧实时比例文案 | `Overtime.CompLeaveRatio` |
-| 开始时间 | 点击 | `TDDatePicker` 年月日时分 | `Overtime.StartTime` |
-| 结束时间 | 点击 | `TDDatePicker`;确认后自动计算净工时(扣除 12:00-13:00/18:00-18:30) | `Overtime.EndTime` → `NetOtHours` |
-| 净工时卡片 | — | 只读大字;≤0→变红+提交按钮置灰 | `Overtime.NetOtHours` |
-| 开始>结束校验 | — | 边框变红+提示+提交按钮置灰 | — |
-| 加班原因 `TDTextarea` | 输入 | 大文本 | `Overtime.Reason` |
-| [存为草稿]/[提交审批] | 点击 | 复用页面 4 逻辑 | — |
-| 返回按钮(有未保存修改) | 点击 | 复用页面 4 逻辑 | — |
-
----
-
-## 页面 11:加班申请列表页 (`/overtime/list`)
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| `TDChip`(全部/草稿/审批中/已通过/已拒绝/已撤回) | 点击 | 同列表页标准逻辑 |
-| 补偿方式展示 | — | `overtime_pay`→结算加班费 / `comp_leave`→转调休 / `mixed`→"X%调休+Y%结算" |
-| 其余交互 | — | 同页面 6/7 标准列表逻辑 |
-
-## 页面 12:加班申请详情页 (`/overtime/detail/:id`)
-
-同页面 8/9 详情节构,员工端仅 `[撤回]`。
-
----
-
-## 页面 13:用车申请表单页 (`/vehicle/apply`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 车牌号 `TDPicker` | 选择 | 全公司车池(空闲+使用中);冲突车辆标红"冲突";无可用→`TDEmpty` | `SysVehicle` (IsDeleted=0) |
-| 车牌号+出车/还车时间 | 失焦 | 异步排期冲突检测;冲突→红色文本+建议文字+锁死提交按钮 | `Vehicle` (Status IN pending,approved) |
-| 用车事由 | 输入 | 文本 | `Vehicle.Reason` |
-| 预计始发地 | 定位/输入 | 原生定位匹配 | `Vehicle.Origin` |
-| 预计目的地+地图图标 | 输入/点图标 | MethodChannel 唤醒地图选点→回填地址+经纬度 | `Vehicle.Destination` + `DestLongitude/Latitude` |
-| 预计出车时间 | 点击 | `TDDatePicker` 年月日时分 | `Vehicle.StartTime` |
-| 预计还车时间 | 点击 | `TDDatePicker` 年月日时分 | `Vehicle.EndTime` |
-| 还车>出车校验 | — | 否则红色提示+提交按钮置灰 | — |
-| 同行总人数 | 输入 | 纯数字,0→1 | `Vehicle.PassengerCount` |
-| 随行同行人 [+] | 点击 | MethodChannel 唤醒通讯录多选→胶囊标签展示;取消→无变化 | `VehiclePassenger` |
-| 胶囊标签 x | 点击 | 剔除该人员 | — |
-| [存为草稿]/[提交审批] | 点击 | 复用页面 4 逻辑 | — |
-
----
-
-## 页面 14:用车申请列表页 (`/vehicle/list`)
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| `TDChip`(全部/草稿/审批中/已通过/已拒绝/已撤回/已还车) | 点击 | 同列表页标准逻辑 |
-| 卡片左滑-`[编辑]`/`[删除]` | 点击 | 仅 draft/rejected |
-| 卡片左滑-`[确认还车]` | 点击 | 仅 approved;拉起半屏核销抽屉(复用页面 15) |
-| 卡片左滑(已还车) | — | 无操作 |
-
----
-
-## 页面 15:用车申请详情页 (`/vehicle/detail/:id`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 微缩静态地图 | 点击 | MethodChannel 唤醒原生地图导航 | `OriginLongitude/Latitude`, `DestLongitude/Latitude` |
-| [确认还车并登记](approved) | 点击 | 滑出核销抽屉 | — |
-| 核销-实还时间 | 点击 | `TDDatePicker` 年月日时分 | `Vehicle.ActualReturnTime` |
-| 核销-出车前里程 | 输入 | 纯数字键盘 | `Vehicle.StartOdometer` |
-| 核销-还车后里程 | 输入 | 纯数字,前端校验≥出车前里程 | `Vehicle.EndOdometer` |
-| 核销-费用备注 | 输入 | 路桥费/停车费 | `Vehicle.ActualCost` + `CostRemark` |
-| 确认提交还车 | 点击 | `TDDialog` 确认→Status='returned'→页面刷新(底部显示归档时间+核销信息) | — |
-| 已还车底部栏 | — | 灰色只读"已还车归档于 YYYY-MM-DD HH:mm" | `UpdateTime` |
-
----
-
-## 页面 16:业务员外出日志创建页 (`/outing-log/create`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| GPS 定位区 | 页面初始化 | 强制请求高精度 GPS → ReadOnly 展示地址;精度>100m→黄色警告 | MethodChannel → `OutingLog.CheckInLongitude/Latitude/Address` |
-| GPS 定位区(失败) | — | `TDEmpty`"无法获取当前位置";提交按钮置灰 | — |
-| 客户名称 `TDInput` | 输入 | 自动联想 `SysCustomer.CustomerName`;无匹配→自由文本(后端自动创建) | `OutingLog.CustomerName` (草稿: CustomerId=NULL) |
-| 今日工作总结 `TDTextarea` | 输入 | 大文本 | `OutingLog.VisitSummary` |
-| 后续推进计划 | 输入 | 大文本 | `OutingLog.NextPlan` |
-| 现场拍照区域 | 点击 | 跳过相册,直接唤醒相机;水印=服务器授时+GPS;满 4 隐藏 | `OutingLogAttachment` |
-| 相机权限被拒 | — | `TDDialog` + "前往设置"按钮 | — |
-| [存为草稿] | 点击 | 复用页面 4 逻辑;GPS、CustomerId 可为空 | — |
-| [提交] | 点击 | 校验 GPS+照片≥1;无→置灰+红色提示 | — |
-| 返回按钮(有未保存修改) | 点击 | 复用页面 4 逻辑 | — |
-
----
-
-## 页面 17:外出日志列表页 (`/outing-log/list`)
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| `TDChip`(全部/本月) | 点击 | 切换刷新 |
-| 卡片"新点评" `TDBadge` | — | 橙色标记,数据来源:`OutingLogComment.CreateTime > COALESCE(LastViewedTime, '1900-01-01')` 且评论者非员工本人 |
-| 卡片左滑-`[编辑]`/`[删除]` | 点击 | 仅 draft;`[删除]`需确认弹窗 |
-| 卡片左滑(completed) | — | 无操作 |
-| 其余交互 | — | 同标准列表逻辑 |
-
-## 页面 18:外出日志详情页 (`/outing-log/detail/:id`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 微缩地图 | 点击 | 唤醒原生地图导航 | `CheckInLongitude/Latitude` |
-| 照片墙缩略图 | 点击 | 全屏预览 | `OutingLogAttachment` |
-| 主管点评区(员工端) | — | 只读浏览,不可回复 | `OutingLogComment` (ORDER BY CreateTime ASC) |
-| 页面进入 | — | 无感 `PUT /api/outing-log/:id/view`,更新 `LastViewedTime`,列表"新点评"红点消失 | `OutingLog.LastViewedTime = NOW()` |
-
-### 经理版增量
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| `TDRate` 星级 | 点选 | 1-5 星 | `OutingLogComment.RatingStars` |
-| 点评文本框 | 输入 | 批示文字 | `OutingLogComment.CommentText` |
-| 发送按钮 | 点击 | 气泡追加+动画,写入 `OutingLogComment`,同步更新 `OutingLog.UpdateTime` | — |
-
----
-
-## 页面 19:公司公告列表页 (`/announcement/list`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 导航栏标题 | — | "公司公告" | — |
-| `TDChip` 类型筛选 | 点击 | 全部/通知公告/人事与制度/放假与活动/我的草稿(仅管理员) | `Announcement.Type` + `Status` |
-| 公告卡片 | 点击 | 路由 → `/announcement/detail/:id` | — |
-| 列表排序 | — | 置顶→未过期(PublishTime DESC)→已过期(PublishTime DESC) | `IsTop`, `PublishTime`, `ExpiryDate` |
-| 已过期卡片 | — | 整体置灰+标题末尾"已过期" | `ExpiryDate < NOW` |
-| 未读红点 | — | 红点淡出(返回列表后) | `AnnouncementReadLog.IsRead=0` |
-| 列表区域 | 下拉/上拉 | 刷新/加载更多 | — |
-| 搜索 | — | 不支持 | — |
-
----
-
-## 页面 20:公司公告详情页 (`/announcement/detail/:id`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| 公告正文 | — | HTML/Markdown 渲染 | `Announcement.Content` |
-| 已过期横幅 | — | 红色"该公告已于 YYYY-MM-DD HH:mm 过期" | `Announcement.ExpiryDate` |
-| 附件图标 | 点击 | 原生浏览器/下载管理器打开;失败→Toast | `AnnouncementAttachment` |
-| 附件列表为空 | — | 整个"附件下载"区块隐藏 | — |
-| 页面停留 ≥2s | — | 无感发送已读请求;<2s 返回不发 | `POST /api/announcements/:id/read` → `IsRead=1` |
-
-### 管理员版增量
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| [已读 N 人] Chip | 点击 | 展开已读员工列表(头像+部门) | `AnnouncementReadLog` (IsRead=1) |
-| [未读 N 人] Chip | 点击 | 展开未读员工列表(头像+部门) | `AnnouncementReadLog` (IsRead=0) |
-| [一键 DING] | 点击 | 震动反馈→封装 UserIds→MethodChannel 强推 Push/短信 | `AnnouncementReadLog.IsUrged=1`, `LastUrgeTime` |
-
----
-
-## 页面 21~25:个人五大明细报表页 (`/report/..`)
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| `TDDropdownMenu`(本月/本季/本年) | 选择 | 数值卡片+图表 300ms 渐变刷新;不支持自定义日期 |
-| 数值卡片 | — | 只读;空→0/¥0.00 |
-| `fl_chart` 数据点 | 长按 | tooltip(日期+数值) |
-| `fl_chart` 图表 | 水平滑动 | 查看历史数据 |
-| 员工版数据导出 | — | 不支持 |
-| 图表为空 | — | `TDEmpty`:"所选时间范围内暂无数据" |
-
-### 经理版增量
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| 柱状图柱体 | 点击 | 下方列表联动过滤→该员工本月单据流水 |
-| 流水条目 | 点击 | 穿透跳转对应单据详情页 |
-
-### 财务版增量(仅页面 22)
-
-| 元素 | 事件 | 行为 |
-|------|------|------|
-| 部门树/项目组筛选器 | 级联选择 | 多维组合筛选 |
-| [数据流水一键导出] | 点击 | Loading→后端生成 Excel→MethodChannel 唤起原生分享面板 |
-
----
-
-## 页面 26:系统公告发布表单页 (`/announcement/create`)
-
-| 元素 | 事件 | 行为 | 数据来源 |
-|------|------|------|-------------|
-| 公告标题 `TDInput` | 输入 | 文本 | `Announcement.Title` |
-| 公告分类下拉 | 选择 | notice/policy/activity | `Announcement.Type` |
-| 富文本编辑域 | 操作 | 加粗/斜体/下划线/有序列表/无序列表/插入图片/插入链接/字号(H1-H3+正文) | `Announcement.Content` |
-| 附件上传 [+] | 点击 | ≤5 个,PDF/图片/Word/Excel,≤20MB | `AnnouncementAttachment` |
-| 置顶 `TDSwitch` | 切换 | 默认关闭 | `Announcement.IsTop` |
-| 有效期 `TDDatePicker` | 选择 | 非必填,不填永不过期 | `Announcement.ExpiryDate` |
-| 接收范围选择器 | 点击 | 右侧滑出部门树多选(Checkbox);底部统计"已选 N 部门,覆盖 M 员工";默认全员 | `AnnouncementTarget` |
-| [预览](导航栏右侧) | 点击 | `TDDialog` 全屏模拟详情页 | — |
-| [存为草稿] | 点击 | 保存→跳转公告列表(草稿仅创建者+管理员可见) | `Announcement.Status='draft'` |
-| [发布] | 点击 | 确认弹窗"确认向 M 名员工发布?"→写入+异步 Job 初始化 `AnnouncementReadLog`→Toast | `Announcement.Status='published'` |
-| 返回按钮(有未保存修改) | 点击 | 复用页面 4 逻辑 | — |
-
----
-
-## 页面 27:全局用户权限配置页 (`/admin/permissions`)
-
-| 元素 | 事件 | 行为 | 数据来源/接口 |
-|------|------|------|-------------|
-| `TDSearchBar` | 输入 | 300ms 防抖模糊搜索→骨架屏→结果刷新;无结果→`TDEmpty` | `SysUser.UserName` + `RealName` |
-| 员工列表 | 下拉/上拉 | 刷新/每页 20 条加载更多 | 默认按部门+姓名排序 |
-| 员工卡片 | 点击 | 右侧滑出 `TDDrawer` 权限编辑抽屉 | — |
-| 角色复选框 ×4 `TDCheckboxGroup` | 勾选 | 普通员工/审批人(经理)/财务人员/系统管理员 | `SysUserRole` |
-| 启用/禁用 `TDSwitch` | 切换 | 映射 `IsActive`;经理有待审批时警告确认;取消→回弹 | `SysUser.IsActive` |
-| [确认保存权限修改] | 点击 | Loading→`PUT /api/admin/assign-role`→Toast+写入审计日志;失败→Toast 红色 | `SysRoleChangeLog` |
-| 【变更记录】折叠区 | 展开 | `TDTimeline` 展示最近 20 条变更记录 | `SysRoleChangeLog` (CREATE TIME DESC LIMIT 20) |
-| 抽屉关闭(有未保存修改) | — | 确认弹窗"是否放弃?" | — |
-| 自保护 | — | 管理员无法取消自己的 admin 角色→Toast | — |
-| 最后管理员保护 | — | 移除最后 admin 时后端拒绝 | — |
-
----
-
-## 全局交互规范速查
-
-| 场景 | 行为 |
-|------|------|
-| 所有列表页下拉 | 刷新 `page=1` |
-| 所有列表页上拉触底 | `page++` 追加 |
-| 所有表单页返回(有未保存修改) | `TDDialog` 确认 |
-| 所有表单提交/存草稿 | Loading 态 + 成功/失败 Toast |
-| 所有附件缩略图点击 | 黑底全屏预览,双指缩放,左右滑动 |
-| 所有 PDF 附件点击 | 原生 PDF 查看器 |
-| 并发编辑冲突 | 错误码 `CONCURRENCY_CONFLICT`→`TDDialog`→返回列表刷新 |
-| 并发审批冲突 | 错误码 `APPROVAL_CONFLICT`→详情页自动刷新+Toast |
-| 401 未授权 | 静默触发宿主登录流程 |
-| 横竖屏 | 仅支持竖屏 |
-| Deep Link | `tboss://oa/{path}` → GoRouter 路由 |

+ 0 - 724
docs/superpowers/specs/2026-05-30-tboss-oa-prototype-spec.md

@@ -1,724 +0,0 @@
-# TBOSS OA 模块 — 原型规格补充说明书
-
-> 配合 `2026-05-30-tboss-oa-design.md`、`2026-05-30-tboss-oa-database.md`、`2026-05-30-tboss-oa-interactions.md` 使用,补齐高保真原型生成所需的布局标注、组件状态、Mock 数据、资源清单、权限矩阵和校验规则。
-
----
-
-## 一、页面布局标注
-
-### 1.1 Appshell (`/`)
-
-```
-┌──────────────────────────────────────────┐
-│  [IndexedStack 主内容区]                  │
-│                                          │
-│                                          │
-├──────────────────────────────────────────┤
-│  TDTabBar (h=56px, bg=#FFFFFF)           │
-│  [🔔消息]   [📱工作台]   [👤我的]        │
-│   flex=1     flex=1      flex=1          │
-│   TDBadge 在图标右上角, offset(-4,-4)    │
-└──────────────────────────────────────────┘
-```
-
-### 1.2 消息通知 (`/messages`)
-
-```
-┌──────────────────────────────────────────┐
-│ TDNavbar: "消息通知"      [全部已读]      │
-├──────────────────────────────────────────┤
-│ TDListView (padding-h=16px, gap=8px)     │
-│ ┌──────────────────────────────────────┐ │
-│ │ [图标 24×24] 标题(14sp, bold)        │ │
-│ │              发送人(12sp)  时间(12sp) │ │
-│ │              摘要(12sp, --text-sec)   │ │
-│ │                              [红点 8×8]│ │
-│ └──────────────────────────────────────┘ │
-│ (卡片 h=auto, bg=#FFF, r=8px,           │
-│  padding=12px, 可左滑)                   │
-└──────────────────────────────────────────┘
-```
-
-### 1.3 工作台 (`/home`)
-
-```
-┌──────────────────────────────────────────┐
-│ TDNavbar: "TBOSS 工作台"                 │
-├──────────────────────────────────────────┤
-│ TDRotation (h=160px, 圆角8px)            │
-│   [图片1] [图片2] [图片3]  ··· ●○○      │
-├──────────────────────────────────────────┤
-│ TDGrid (4列, gap=12px, padding-h=16px)   │
-│ ┌────┐ ┌────┐ ┌────┐ ┌────┐            │
-│ │图标│ │图标│ │图标│ │图标│            │
-│ │事前│ │费用│ │用车│ │加班│   ← 发起行   │
-│ │申请│ │报销│ │申请│ │申请│            │
-│ └────┘ └────┘ └────┘ └────┘            │
-│ ┌────┐ ┌────┐ ┌────┐ ┌────┐            │
-│ │图标│ │图标│ │图标│ │图标│            │
-│ │申请│ │报销│ │外勤│ │公司│   ← 记录行   │
-│ │记录│ │记录│ │日志│ │公告│            │
-│ └────┘ └────┘ └────┘ └────┘            │
-│ (每格 icon=32×32, text=12sp)            │
-├──────────────────────────────────────────┤
-│ 【我的快捷看板】卡片 (h=120px)           │
-│ ┌─45%─┐ ┌─25%─┐ ┌─30%─┐               │
-│ │¥12,350│ │  8  │ │  2  │               │
-│ │本月报销│ │已提单│ │待处理│               │
-│ └──────┘ └─────┘ └─────┘               │
-└──────────────────────────────────────────┘
-```
-
-### 1.4 个人中心 (`/profile`)
-
-```
-┌──────────────────────────────────────────┐
-│ TDNavbar: "我的"                         │
-├──────────────────────────────────────────┤
-│ ┌──────────────────────────────────────┐ │
-│ │ [头像 64×64, r=32]                    │ │
-│ │ 张三          销售部 · 客户经理       │ │
-│ │ (padding=16px, bg=#FFF)              │ │
-│ └──────────────────────────────────────┘ │
-├──────────────────────────────────────────┤
-│ TDListView (分组样式, gap=0, 分割线1px)  │
-│  我的审批历史                    ›       │
-│ ───────────────────────────────         │
-│  我的报表                        ›       │
-│ ───────────────────────────────         │
-│  关于 TBOSS OA                   ›       │
-│ (每行 h=48px, padding-h=16px)           │
-└──────────────────────────────────────────┘
-```
-
-### 1.5 表单页布局(页面 4/5/10/13 通用)
-
-```
-┌──────────────────────────────────────────┐
-│ TDNavbar: "{表单名称}"                    │
-├──────────────────────────────────────────┤
-│ FormSection: 基本信息 (padding=16px)      │
-│ ┌──────────────────────────────────────┐ │
-│ │ 申请人姓名    [只读]    所属部门  [只读]│ │
-│ │ (两列并排, 各 flex=1, gap=12px)      │ │
-│ │ 申请日期      [只读]                  │ │
-│ │ 紧急程度  ○普通 ○紧急 ○特急           │ │
-│ │ 费用类型  [ 下拉选择  ▼ ]             │ │
-│ │ 费用事由  [                    ]      │ │
-│ │           (多行, h=80px)              │ │
-│ └──────────────────────────────────────┘ │
-│                                          │
-│ FormSection: 关联管控                     │
-│ ┌──────────────────────────────────────┐ │
-│ │ 关联项目      [ 级联选择  ▼ ]         │ │
-│ │ 预算科目      [ 级联选择  ▼ ]         │ │
-│ │ 💰 当前可用预算余额:¥50,000.00       │ │
-│ └──────────────────────────────────────┘ │
-│                                          │
-│ 支撑材料上传区                            │
-│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐        │
-│ │+ │ │  │ │  │ │  │ │  │ │  │  TDImageGrid│
-│ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘  3列, gap=8│
-│ (每格 100×100, r=4px)                    │
-│                                          │
-├──────────────────────────────────────────┤
-│ [重置]   [存为草稿]      [ 提交审批 ]     │
-│ (灰色)   (浅蓝#E6F7FD)   (主色#00ABF3)   │
-│ 底部栏 h=56px, bg=#FFF, shadow-up        │
-└──────────────────────────────────────────┘
-```
-
-### 1.6 详情页布局(页面 8/9 通用)
-
-```
-┌──────────────────────────────────────────┐
-│ TDNavbar: "{单据}详情"                    │
-├──────────────────────────────────────────┤
-│ 状态大色块横幅 (h=120px)                  │
-│ ┌──────────────────────────────────────┐ │
-│ │         ✅ (icon 48×48)              │ │
-│ │        已通过                        │ │
-│ │    当前审批人:王经理                  │ │
-│ │ (bg=success时#E6F9F2, text=#00A870)   │ │
-│ └──────────────────────────────────────┘ │
-│ 提交时间:2026-05-30 14:30               │
-├──────────────────────────────────────────┤
-│ 基础信息 (key-value 双栏, 左灰右黑)       │
-│  申请人      │ 张三                      │
-│  所属部门    │ 销售部                     │
-│  费用类型    │ 差旅费                     │
-│  预估金额    │ ¥12,350.00                 │
-│  (左列 w=100px, 12sp, --text-secondary)  │
-├──────────────────────────────────────────┤
-│ 费用明细表 (明细>5行折叠)                 │
-│ ┌──────┬──────┬──────┬──────┐          │
-│ │费用类型│预估金额│ 说明 │      │          │
-│ ├──────┼──────┼──────┼──────┤          │
-│ │ ...  │ ...  │ ...  │      │          │
-│ └──────┴──────┴──────┴──────┘          │
-│            [ 展开全部 ▼ ]                │
-├──────────────────────────────────────────┤
-│ 附件宫格 TDImageGrid (3列)               │
-├──────────────────────────────────────────┤
-│ TDTimeline 审批流                         │
-│ ● 发起人已提交  2026-05-30 14:30         │
-│ │  (左竖线 2px, --border)               │
-│ ● 王经理已通过  2026-05-30 15:00         │
-│ │                                        │
-│ ○ 李会计审批中  (待处理)                  │
-├──────────────────────────────────────────┤
-│ [编辑]          [ 提交审批 ]              │
-│ 底部栏 h=56px, bg=#FFF                   │
-└──────────────────────────────────────────┘
-```
-
-### 1.7 列表页布局(页面 6/7/11/14 通用)
-
-```
-┌──────────────────────────────────────────┐
-│ TDNavbar: "{列表名称}"                    │
-├──────────────────────────────────────────┤
-│ TDChip 滚动条 (h=44px)                    │
-│ [全部] [草稿] [审批中] [已通过] [已拒绝]  │
-│ (gap=8px, padding-h=16px, 可水平滚动)     │
-├──────────────────────────────────────────┤
-│ TDListView (padding-h=16px, gap=12px)    │
-│ ┌──────────────────────────────────────┐ │
-│ │ BXSQ-20260530-001                    │ │
-│ │ 差旅费报销 — 深圳出差                 │ │
-│ │ ¥12,350.00          2026-05-30       │ │
-│ │                           [已通过]    │ │
-│ │ (h=auto, bg=#FFF, r=8px, p=12px)    │ │
-│ └──────────────────────────────────────┘ │
-│ ┌──────────────────────────────────────┐ │
-│ │ ...                                  │ │
-│ └──────────────────────────────────────┘ │
-└──────────────────────────────────────────┘
-```
-
----
-
-## 二、组件状态矩阵
-
-### 2.1 按钮状态
-
-| 状态 | 样式 |
-|------|------|
-| 默认 | `bg=--primary(#00ABF3)`, `text=#FFF`, `fs=16sp`, `h=44px`, `r=22px` |
-| 按下 | `bg=--primary-active(#0089C4)`, 缩放 0.97 |
-| 禁用 | `bg=#E0E0E0`, `text=#BFBFBF`, 不可点击 |
-| Loading | 文本替换为 20×20 白色菊花 `TDLoading`, 按钮宽度不变 |
-
-**二级按钮(存为草稿/重置)**:
-| 状态 | 样式 |
-|------|------|
-| 默认 | `bg=--primary-light(#E6F7FD)`, `text=--primary(#00ABF3)`, 边框 1px `--primary` |
-| 按下 | `bg=#CCEFFC` |
-
-**危险按钮(删除/拒绝/强制终止)**:
-| 状态 | 样式 |
-|------|------|
-| 默认 | `bg=#FFF`, `text=--danger(#D54941)`, 边框 1px `--danger` |
-| 按下 | `bg=#FFF0EF` |
-
-### 2.2 输入框状态
-
-| 状态 | 样式 |
-|------|------|
-| 默认 | `bg=#FFF`, `border=1px --border(#E7E7E7)`, `r=4px`, `h=44px`, `padding-h=12px` |
-| 聚焦 | `border=1.5px --primary(#00ABF3)`, 外阴影 `0 0 0 2px rgba(0,171,243,0.1)` |
-| 报错 | `border=1.5px --danger(#D54941)`, 下方红色提示文字 12sp |
-| 只读 | `bg=--bg-page(#F5F5F5)`, `text=--text-secondary(#666666)`, 无边框 |
-
-### 2.3 TDChip 状态
-
-| 状态 | 样式 |
-|------|------|
-| 默认 | `bg=#F5F5F5`, `text=--text-secondary(#666666)`, `h=32px`, `r=16px`, `padding-h=12px` |
-| 选中 | `bg=--primary(#00ABF3)`, `text=#FFF` |
-
-### 2.4 卡片左滑
-
-| 状态 | 样式 |
-|------|------|
-| 默认 | 正常位置, `offsetX=0` |
-| 左滑露出操作 | 卡片主体左移 120px, 右侧露出按钮区 `w=120px`, 弹簧动画 200ms |
-| 删除动画 | 卡片 `opacity 0→1 200ms` + `height→0 300ms`, 下方卡片上移 |
-
-### 2.5 TDBadge 状态
-
-| 变体 | 样式 |
-|------|------|
-| 纯红点 | `w=8px, h=8px, r=4px, bg=--danger`, 无文字 |
-| 数字角标 | `h=18px, min-w=18px, r=9px, bg=--danger, text=#FFF, fs=10sp`, 数字>99 显示"99+" |
-| 消失动画 | `scale 1→0 + opacity 1→0, 200ms` |
-
-### 2.6 StatusTag 状态
-
-| 状态 | 样式 |
-|------|------|
-| 草稿 (draft) | `bg=#F5F5F5, text=--status-gray(#999999)` |
-| 审批中 (pending) | `bg=#FFF3E8, text=--warning(#E37318)` |
-| 已通过 (approved) | `bg=#E6F9F2, text=--success(#00A870)` |
-| 已拒绝 (rejected) | `bg=#FFF0EF, text=--danger(#D54941)` |
-| 已撤回 (withdrawn) | `bg=#F5F5F5, text=--status-gray(#999999)` |
-| 已还车 (returned) | `bg=#EDF2FC, text=#5A8CDB` |
-| 已完成 (completed) | `bg=#E6F9F2, text=--success(#00A870)` |
-
----
-
-## 三、数据 Mock 结构
-
-### 3.1 消息列表项
-
-```json
-{
-  "id": "msg-001",
-  "msgType": "approval_notice",
-  "bizType": "expense",
-  "bizId": "exp-001",
-  "title": "王经理提交了一笔费用报销",
-  "sender": {
-    "id": "user-002",
-    "realName": "王经理",
-    "avatarUrl": "/avatars/user-002.jpg"
-  },
-  "content": "差旅费报销 — 深圳出差",
-  "isRead": false,
-  "createTime": "2026-05-30T14:30:00Z"
-}
-```
-
-### 3.2 列表卡片(事前申请)
-
-```json
-{
-  "applicationNo": "BXSQ-20260530-001",
-  "purpose": "深圳客户拜访差旅费",
-  "estimatedAmount": 12350.00,
-  "expenseType": "travel",
-  "status": "approved",
-  "createTime": "2026-05-30T10:00:00Z"
-}
-```
-
-### 3.3 详情页(事前申请)
-
-```json
-{
-  "id": "ea-001",
-  "applicationNo": "BXSQ-20260530-001",
-  "status": "approved",
-  "createTime": "2026-05-30T10:00:00Z",
-  "applicant": { "realName": "张三", "deptName": "销售部" },
-  "urgency": "normal",
-  "expenseType": "travel",
-  "projectName": "2026华南市场拓展",
-  "budgetSubjectName": "差旅费",
-  "estimatedAmount": 12350.00,
-  "purpose": "深圳客户拜访差旅费",
-  "currentApprover": { "realName": "王经理", "deptName": "销售部", "position": "销售总监" },
-  "details": [
-    { "expenseCategory": "交通费", "estimatedAmount": 8000.00, "remark": "往返机票" },
-    { "expenseCategory": "住宿费", "estimatedAmount": 3500.00, "remark": "三晚酒店" },
-    { "expenseCategory": "餐饮费", "estimatedAmount": 850.00, "remark": "客户招待" }
-  ],
-  "attachments": [
-    { "fileName": "出差申请单.pdf", "fileUrl": "/files/ea-001-1.pdf", "fileType": "pdf", "fileSize": 204800 },
-    { "fileName": "报价单.jpg", "fileUrl": "/files/ea-001-2.jpg", "fileType": "image", "fileSize": 512000 }
-  ],
-  "approvalTimeline": [
-    { "approvalLevel": 0, "action": "提交", "approver": { "realName": "张三" }, "createTime": "2026-05-30T10:00:00Z", "opinion": "" },
-    { "approvalLevel": 1, "action": "approve", "approver": { "realName": "王经理" }, "createTime": "2026-05-30T14:00:00Z", "approvalTime": "2026-05-30T15:00:00Z", "opinion": "同意" }
-  ],
-  "availableBudget": 50000.00
-}
-```
-
-### 3.4 待审批卡片(经理版)
-
-```json
-{
-  "pendingCount": 8,
-  "recentItems": [
-    { "applicantName": "张三", "bizTypeName": "费用报销", "amount": 12350.00, "createTime": "2026-05-30T10:00:00Z" },
-    { "applicantName": "李四", "bizTypeName": "事前申请", "amount": 5000.00, "createTime": "2026-05-30T09:30:00Z" },
-    { "applicantName": "赵五", "bizTypeName": "加班申请", "amount": null, "otHours": 4.5, "createTime": "2026-05-30T09:00:00Z" }
-  ]
-}
-```
-
-### 3.5 财务看盘卡片
-
-```json
-{
-  "monthPaidAmount": 285600.00,
-  "pendingPaymentAmount": 152300.00,
-  "weekAbnormalInvoices": 3
-}
-```
-
-### 3.6 快捷看板
-
-```json
-{
-  "monthExpenseTotal": 12350.00,
-  "monthApplicationCount": 8,
-  "pendingProcessCount": 2
-}
-```
-
-### 3.7 公告触达率
-
-```json
-{
-  "announcementId": "ann-001",
-  "totalTargets": 50,
-  "readCount": 45,
-  "unreadCount": 5,
-  "unreadUsers": [
-    { "id": "user-010", "realName": "钱六", "deptName": "财务部", "avatarUrl": "/avatars/user-010.jpg" },
-    { "id": "user-011", "realName": "孙七", "deptName": "行政部", "avatarUrl": "/avatars/user-011.jpg" }
-  ]
-}
-```
-
-### 3.8 权限管理列表项
-
-```json
-{
-  "id": "user-003",
-  "userName": "0048",
-  "realName": "张三",
-  "deptName": "销售部",
-  "avatarUrl": "/avatars/user-003.jpg",
-  "isActive": true,
-  "roles": ["employee"],
-  "roleChangeLog": [
-    {
-      "operatorName": "管理员",
-      "changeType": "assign_role",
-      "beforeSnapshot": ["employee"],
-      "afterSnapshot": ["employee", "finance"],
-      "createTime": "2026-05-28T11:00:00Z"
-    }
-  ]
-}
-```
-
----
-
-## 四、资源清单
-
-### 4.1 默认头像占位
-
-| 场景 | 样式 |
-|------|------|
-| 无头像用户 | 圆形,`bg=--primary(#00ABF3)`,居中白色姓名首字母(如"张"),`fs=18sp` |
-| 已离职/软删除用户 | 圆形,`bg=--status-gray(#999999)`,首字母,整体透明度 0.5 |
-
-### 4.2 空状态占位图
-
-| 场景 | 图标 | 尺寸 | 文案 |
-|------|------|------|------|
-| 消息为空 | 铃铛(`TDIcons.notification`) | 80×80, `color=--text-placeholder` | "暂无消息通知" |
-| 列表为空 | 文档(`TDIcons.task`) | 80×80 | "暂无记录" |
-| 待审批为空 | 对勾(`TDIcons.check-circle`) | 80×80 | "暂无待审批单据" |
-| 明细为空 | 票据(`TDIcons.receipt`) | 60×60 | "暂无报销明细" |
-| 附件为空 | 图片(`TDIcons.image`) | 60×60 | "暂无附件" |
-| 搜索为空 | 搜索(`TDIcons.search`) | 80×80 | "未找到匹配的员工" |
-| 公告为空 | 喇叭(`TDIcons.notification`) | 80×80 | "暂无行政公告" |
-| 报表为空 | 图表(`TDIcons.chart`) | 100×100 | "所选时间范围内暂无数据" |
-
-### 4.3 状态横幅图标
-
-| 状态 | 图标 | 尺寸 | 底色 |
-|------|------|------|------|
-| 审批中 | `TDIcons.time-filled` | 48×48, `color=--warning` | `bg=#FFF3E8` |
-| 已通过 | `TDIcons.check-circle-filled` | 48×48, `color=--success` | `bg=#E6F9F2` |
-| 已拒绝 | `TDIcons.close-circle-filled` | 48×48, `color=--danger` | `bg=#FFF0EF` |
-| 已撤回 | `TDIcons.rollback` | 48×48, `color=--status-gray` | `bg=#F5F5F5` |
-
-### 4.4 财务归档盖章
-
-| 元素 | 样式 |
-|------|------|
-| "已归档"盖章 | 红色椭圆边框(`border=3px --danger, r=50%`),旋转 -15°,文字`--danger 16sp bold`,透明度 0.7 |
-| 位置 | 详情页发票区右上角,绝对定位 offset(16,-16) |
-
-### 4.5 防伪水印
-
-| 属性 | 值 |
-|------|------|
-| 位置 | 照片右下角 |
-| 内容 | `{YYYY-MM-DD HH:mm:ss}` + `{纬度},{经度}` |
-| 样式 | 半透明白色(`rgba(255,255,255,0.6)`),`fs=10sp`,等宽字体,两行 |
-
-### 4.6 财务凭证表单
-
-```
-┌──────────────────────────────────────┐
-│        财务凭证编号归档               │
-│                                      │
-│ 公司转账电汇流水号 *                  │
-│ ┌──────────────────────────────────┐ │
-│ │ BOF2026053000001234              │ │
-│ └──────────────────────────────────┘ │
-│                                      │
-│ 财务记账凭证号 *                      │
-│ ┌──────────────────────────────────┐ │
-│ │ JZ-2026-05-0032                  │ │
-│ └──────────────────────────────────┘ │
-│                                      │
-│         [ 确认归档 ]                  │
-└──────────────────────────────────────┘
-```
-
----
-
-## 五、角色权限矩阵
-
-### 5.1 页面可见性
-
-| 页面 | 员工 | 经理 | 财务 | 管理员 |
-|------|:--:|:--:|:--:|:--:|
-| `/` (Appshell) | ✅ | ✅ | ✅ | ✅ |
-| `/messages` | ✅ | ✅ | ✅ | ✅ |
-| `/home` | 员工版 | +待审批卡片 | +财务看盘 | +公告入口 |
-| `/profile` | ✅ | ✅ | ✅ | ✅ |
-| `/expense-apply/apply` | ✅ | ✅ | — | — |
-| `/expense/apply` | ✅ | ✅ | — | — |
-| `/expense-apply/list` | 我的 | +下属标签 | 全公司(只读) | — |
-| `/expense/list` | 我的 | +下属标签 | +待付款Chip | — |
-| `/expense-apply/detail/:id` | 员工版 | 经理版 | — | 管理员版 |
-| `/expense/detail/:id` | 员工版 | 经理版 | 财务版 | 管理员版 |
-| `/overtime/apply` | ✅ | ✅ | — | — |
-| `/overtime/list` | 我的 | +下属标签 | 全公司(只读) | — |
-| `/overtime/detail/:id` | 员工版 | 经理版 | — | 管理员版 |
-| `/vehicle/apply` | ✅ | ✅ | — | — |
-| `/vehicle/list` | 我的 | +下属标签 | 全公司(只读) | — |
-| `/vehicle/detail/:id` | 员工版 | 经理版 | — | 管理员版 |
-| `/outing-log/create` | ✅ | ✅ | — | — |
-| `/outing-log/list` | 我的 | +下属标签 | — | — |
-| `/outing-log/detail/:id` | 员工版 | +点评区 | — | — |
-| `/announcement/list` | ✅ | ✅ | ✅ | +我的草稿Chip |
-| `/announcement/detail/:id` | ✅ | ✅ | ✅ | +触达审计 |
-| `/report/*` | 个人 | 部门 | 全公司+导出 | — |
-| `/announcement/create` | — | — | — | ✅ |
-| `/admin/permissions` | — | — | — | ✅ |
-
-### 5.2 角色判定优先级
-
-```
-Admin > Finance > Approver > Employee
-```
-
-高优先级角色叠加低优先级角色的功能块。例如:用户同时拥有 Approver + Employee → 展示经理版工作台 + 员工版列表切换。
-
----
-
-## 六、表单校验规则汇总
-
-### 6.1 事前申请表单
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 费用事由 | 必填,≤200字 | 提交时 | ScrollTo + 红框 + `TDMessage`"请填写费用事由" |
-| 关联项目 | 草稿可空,提交必填 | 提交时 | 同上"请选择关联项目" |
-| 预算科目 | 草稿可空,提交必填 | 提交时 | 同上"请选择预算科目" |
-| 预估金额合计 | 可超预算,仅警告 | 实时 | 金额变红 + 警告文本"已超支,将触发高管特批" |
-| 单张图片 | ≤10MB | 上传时 | `TDToast`"文件大小超过限制" |
-| 单份 PDF | ≤20MB | 上传时 | 同上 |
-
-### 6.2 费用报销表单
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 银行账号 | 16-19位数字 | 实时(前端) + 提交(后端) | 红色提示"请输入正确的银行账号" |
-| 发票金额 | 纯数字 | 输入时 | 键盘限制,不接受非数字 |
-| 关联项目 | 草稿可空,提交必填 | 提交时 | ScrollTo 报错 |
-| 预算科目 | 草稿可空,提交必填 | 提交时 | 同上 |
-| 单张发票图片 | ≤10MB | 上传时 | Toast 提示 |
-
-### 6.3 加班申请表单
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 净工时 | >0 | 实时 | 显示"0小时"+变红+提交按钮置灰 |
-| 开始时间 < 结束时间 | 不晚于 | 实时 | 边框变红+提示+提交按钮置灰 |
-| 混合比例 | 10%-90% | 滑块限制 | 滑块无法拖出范围 |
-
-### 6.4 用车申请表单
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 还车时间 > 出车时间 | 不晚于 | 实时 | 红色提示+提交按钮置灰 |
-| 车牌+时间排期冲突 | 无重叠预订 | 异步校验 | 红色文本"排期冲突"+提交按钮锁死 |
-| 同行人数输入0 | 自动调整 | 失焦 | 0→1 |
-
-### 6.5 还车核销
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 还车里程 ≥ 出车里程 | 不小于 | 实时 | 红色提示"还车里程不能小于出车里程"+提交按钮置灰 |
-
-### 6.6 外勤日志
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| GPS 定位 | 提交时必填 | 提交时 | 提交按钮置灰+"请先获取当前位置" |
-| 现场拍照 | ≥1张 | 提交时 | 提交按钮置灰+"请至少拍摄一张现场照片" |
-
-### 6.7 审批操作
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 拒绝理由 | ≥5字(汉字) | 弹窗内输入 | 确定按钮置灰+提示"请输入至少5个字" |
-
-### 6.8 财务核销
-
-| 字段 | 规则 | 触发时机 | 违反行为 |
-|------|------|---------|---------|
-| 合规复选框 ×3 | 全部勾选 | 页面加载 | 打款按钮置灰(视觉锁定) |
-| 电汇流水号 | 必填 | 提交时 | 红色边框+提示"请输入公司转账电汇流水号" |
-| 记账凭证号 | 必填 | 提交时 | 红色边框+提示"请输入财务记账凭证号" |
-
----
-
-## 七、页面完整 Mock 数据示例
-
-### 7.1 工作台首页完整响应
-
-```json
-{
-  "banners": [
-    { "imageUrl": "/banners/1.jpg", "title": "2026年Q2全员大会", "linkUrl": "/announcement/detail/ann-005" },
-    { "imageUrl": "/banners/2.jpg", "title": "端午节放假通知", "linkUrl": null },
-    { "imageUrl": "/banners/3.jpg", "title": "安全生产月活动", "linkUrl": "/announcement/detail/ann-006" }
-  ],
-  "quickStats": {
-    "monthExpenseTotal": 12350.00,
-    "monthApplicationCount": 8,
-    "pendingProcessCount": 2
-  },
-  "managerPending": {
-    "pendingCount": 8,
-    "recentItems": [
-      { "applicantName": "张三", "bizTypeName": "费用报销", "amount": 12350.00, "createTime": "2026-05-30T10:00:00Z" }
-    ]
-  },
-  "financeDashboard": {
-    "monthPaidAmount": 285600.00,
-    "pendingPaymentAmount": 152300.00,
-    "weekAbnormalInvoices": 3
-  }
-}
-```
-
-### 7.2 消息列表完整响应
-
-```json
-{
-  "total": 25,
-  "page": 1,
-  "unreadCount": 5,
-  "items": [
-    {
-      "id": "msg-001",
-      "msgType": "approval_notice",
-      "bizType": "expense",
-      "bizId": "exp-001",
-      "title": "张三提交了一笔费用报销",
-      "sender": { "realName": "张三", "avatarUrl": "/avatars/u-001.jpg" },
-      "content": "差旅费报销 — 深圳出差 ¥12,350.00",
-      "isRead": false,
-      "createTime": "2026-05-30T14:30:00Z"
-    },
-    {
-      "id": "msg-002",
-      "msgType": "approval_result",
-      "bizType": "expense_apply",
-      "bizId": "ea-003",
-      "title": "王经理拒绝了您的事前申请",
-      "sender": { "realName": "王经理", "avatarUrl": "/avatars/u-002.jpg" },
-      "content": "预算不足,请调整后重新提交",
-      "isRead": true,
-      "createTime": "2026-05-29T16:00:00Z"
-    },
-    {
-      "id": "msg-003",
-      "msgType": "announcement",
-      "bizType": "announcement",
-      "bizId": "ann-005",
-      "title": "2026年Q2全员大会通知",
-      "sender": { "realName": "系统管理员", "avatarUrl": "/avatars/u-admin.jpg" },
-      "content": "定于6月5日下午3点在3楼会议室召开全员大会",
-      "isRead": false,
-      "createTime": "2026-05-30T08:00:00Z"
-    }
-  ]
-}
-```
-
----
-
-## 八、导航关系图(带权限分支)
-
-```
-/ (Appshell)
-│
-├── /messages ─────────────────────────────────────────────────
-│   │ (审批待办) → /{bizType}/detail/:id  (底部带审批操作栏)
-│   │ (审批结果) → /{bizType}/detail/:id
-│   │ (公告通知) → /announcement/detail/:id
-│   │ (纯通知)   → TDDialog 弹窗
-│   │
-│   └── 全部已读: POST /api/messages/read-all
-│       标记已读: PUT /api/messages/:id/read
-│       删除消息: PUT /api/messages/:id/delete
-│
-├── /home ───────────────────────────────────────────────────
-│   │ [员工] 轮播图 + 金刚区(8格) + 快捷看板(3卡片)
-│   │ [经理] +待审批卡片区 (→ /messages?filter=approval_notice)
-│   │ [财务] +全公司财务看盘
-│   │
-│   ├── 金刚区-发起 → /expense-apply/apply | /expense/apply
-│   │                 /vehicle/apply        | /overtime/apply
-│   ├── 金刚区-记录 → /expense-apply/list   | /expense/list
-│   │                 /outing-log/list      | /announcement/list
-│   ├── 看板-已提   → /expense-apply/list
-│   ├── 看板-报销   → /report/expense-detail
-│   └── 看板-待处理 → /messages
-│
-├── /profile ──────────────────────────────────────────────────
-│   ├── 我的审批历史 (子页面)
-│   │   └── 点击单据号 → /{bizType}/detail/:id
-│   ├── 我的报表 (子页面, 5 Tab)
-│   │   └── → /report/{bizType}-detail
-│   ├── 关于 (子页面)
-│   └── 头像上传: PUT /api/user/avatar
-│
-├── /expense-apply/apply ──(存草稿/提交)──→ /expense-apply/list
-├── /expense/apply ────────(存草稿/提交)──→ /expense/list
-├── /vehicle/apply ────────(存草稿/提交)──→ /vehicle/list
-├── /overtime/apply ───────(存草稿/提交)──→ /overtime/list
-├── /outing-log/create ────(存草稿/提交)──→ /outing-log/list
-│
-├── /announcement/create [仅管理员] ────→ /announcement/list
-├── /admin/permissions [仅管理员]
-│
-└── 报表 /report/{bizType}-detail [员工/经理/财务]
-```
-
-**表单→列表→详情 核心流转**:
-```
-apply ──存为草稿──→ list (激活"草稿"Chip) ──左滑编辑──→ apply?id=xxx
-  │                                          │
-  └──提交审批──→ list (激活"全部"Chip)       └──点击卡片──→ detail/:id
-                                                             │
-                    rejected ←── 经理拒绝                      │
-                    approved  ←── 经理同意                     │
-                    withdrawn ←── 员工撤回                     │
-                                                             │
-                    └── rejected → 重新编辑 → apply?id=xxx (更新原记录)
-```

File diff suppressed because it is too large
+ 1141 - 0
docs/superpowers/specs/tboss-oa-api.md


+ 818 - 0
docs/superpowers/specs/tboss-oa-architecture.md

@@ -0,0 +1,818 @@
+# TBOSS OA 模块 — 系统架构设计
+
+> 版本:v1.0 | 日期:2026-06-03 | 基于 `tboss-oa-product-strategy.md`
+
+---
+
+## 1. 整体分层架构
+
+```mermaid
+graph TB
+    subgraph 客户端层["客户端层 — Flutter 3.38.10"]
+        direction LR
+        A1[Android 宿主<br/>Java/Kotlin]
+        A2[iOS 宿主<br/>OC / Xcode 14.2]
+        A3[Flutter OA Module<br/>业务表单 / 公告 / 报表 / 权限管理]
+        A1 --- A3
+        A2 --- A3
+    end
+
+    subgraph 通道层["Platform Channel"]
+        B1[MethodChannel<br/>相机 / 相册 / 通讯录<br/>地图选点 / GPS 定位]
+    end
+
+    subgraph 服务端层[".NET Framework 4.8 服务端"]
+        direction TB
+        C1["OA API Controller<br/>RESTful HTTP API"]
+
+        subgraph 业务服务层
+            C2["权限服务<br/>OaPermission / ACL"]
+            C3["单据服务<br/>报销 / 事前 / 加班 / 用车 / 外勤"]
+            C4["公告服务<br/>发布 / 触达审计 / DING"]
+            C5["报表服务<br/>5 大明细报表 / 导出"]
+        end
+
+        subgraph 集成适配层
+            C6["审批适配器<br/>ErpAAdapter / ErpBAdapter"]
+            C7["消息适配器<br/>App Push / 站内消息"]
+            C8["数据适配器<br/>用户 / 部门 / 客户查询"]
+        end
+
+        C1 --> C2
+        C1 --> C3
+        C1 --> C4
+        C1 --> C5
+        C2 --> C8
+        C3 --> C6
+        C3 --> C8
+        C4 --> C7
+    end
+
+    subgraph 数据层["数据层"]
+        D1[("SQL Server 2019+<br/>OA 业务库<br/>单据 / 公告 / 权限 / 车辆")]
+    end
+
+    subgraph 外部系统["外部系统"]
+        direction LR
+        E1["ERP-A<br/>审批引擎 + 主数据"]
+        E2["ERP-B<br/>审批引擎 + 主数据"]
+        E3["ERP-C<br/>审批引擎 + 主数据"]
+    end
+
+    A3 -->|"HTTP REST"| C1
+    A3 -.->|"MethodChannel"| B1
+    C3 --> D1
+    C4 --> D1
+    C2 --> D1
+    C6 -->|"审批 API<br/>(签名不同)"| E1
+    C6 --> E2
+    C6 --> E3
+    C8 -->|"主数据查询"| E1
+    C8 --> E2
+    C8 --> E3
+    C7 -->|"App Push"| A3
+```
+
+### 分层职责
+
+| 层 | 职责 | 不负责 |
+|----|------|--------|
+| **客户端层** | UI 渲染、表单校验、本地缓存、原生能力调用 | 审批逻辑、权限判定、数据聚合 |
+| **通道层** | 桥接 Flutter 与原生 SDK(相机/GPS/通讯录/地图) | 业务逻辑 |
+| **服务端层** | REST API、业务逻辑、多 ERP 适配、消息推送触发 | 最终审批执行(由 ERP 完成) |
+| **数据层** | OA 业务数据持久化 | 用户/组织/客户/审批数据存储 |
+| **外部系统** | 审批引擎、用户主数据、客户主数据 | OA 特有的业务表单 |
+
+---
+
+## 2. 技术选型
+
+| 层 | 技术 | 版本 | 选型理由 |
+|----|------|------|---------|
+| 移动端框架 | **Flutter** | 3.38.10 | 跨平台代码复用;已有 Flutter Module 嵌入 Android/iOS 宿主 |
+| Android 宿主 | Java/Kotlin | — | 现有 ERP App 技术栈,MethodChannel 互调已验证 |
+| iOS 宿主 | Objective-C | Xcode 14.2 | 现有 ERP App 技术栈;Flutter 3.38.10 兼容已验证 |
+| 后端框架 | **.NET Framework 4.8** | 4.8 | 现有服务端技术栈;团队熟悉;已有消息/审批代理模块可复用 |
+| 数据访问 | **Dapper** | — | 轻量高效;复杂 SQL 手写场景优于 EF;与现有服务端一致 |
+| 数据库 | **SQL Server 2019+** | 2019+ | 现有基础设施;与 ERP 同生态 |
+| HTTP 客户端 | **Dio** | — | Flutter 端标准 HTTP 库;拦截器/超时/重试 |
+| 路由 | **GoRouter** | — | 声明式路由;Deep Link 支持 |
+| 状态管理 | Provider / Riverpod | — | 轻量;全局角色/权限状态注入 |
+| UI 组件库 | **TDesign Flutter** | — | 企业级组件库;项目已统一采用 |
+| 图表 | **fl_chart** | — | 报表趋势图/柱状图 |
+| 原生互调 | **MethodChannel** | — | Flutter ↔ 原生双向通信 |
+
+### 不选型的理由
+
+| 未选 | 理由 |
+|------|------|
+| 独立用户体系 | ERP 已有用户/组织数据,避免双写和同步 |
+| 自建审批引擎 | ERP 审批引擎成熟且多套在用,OA 只做消费端 |
+| 自建消息推送 | .NET 服务端已有完整推送链路 |
+| Flutter 独立 App | 必须是 App 内嵌模块,不能是独立应用 |
+| Entity Framework | 团队偏好 Dapper;现有服务端已是 Dapper |
+
+---
+
+## 3. 模块划分
+
+```
+tboss_oa_module/lib/
+├── main.dart                          # 入口
+├── app.dart                           # MaterialApp + GoRouter + Provider 注入
+│
+├── core/
+│   ├── router/app_router.dart         # 路由表 + Deep Link 处理
+│   ├── network/
+│   │   ├── api_client.dart            # Dio 实例(base URL / 拦截器 / 超时)
+│   │   ├── api_response.dart          # 统一响应模型
+│   │   └── api_exception.dart         # 业务异常
+│   ├── auth/
+│   │   └── auth_service.dart          # Token 管理 + 401 处理
+│   ├── theme/
+│   │   ├── app_colors.dart            # 色彩 Token
+│   │   └── app_theme.dart             # ThemeData
+│   ├── utils/
+│   │   ├── date_utils.dart
+│   │   ├── responsive.dart            # 横竖屏适配
+│   │   └── validators.dart            # 表单校验
+│   └── i18n/
+│       ├── app_localizations.dart
+│       └── locale_provider.dart
+│
+├── shared/
+│   ├── models/
+│   │   ├── user_model.dart            # 用户模型(从 ERP API 映射)
+│   │   ├── approval_status.dart       # 审批状态枚举
+│   │   └── pagination_model.dart      # 分页模型
+│   └── widgets/
+│       ├── app_card.dart
+│       ├── status_tag.dart            # 状态标签组件
+│       ├── status_banner.dart         # 详情页状态横幅
+│       ├── list_card.dart             # 列表卡片
+│       ├── action_bar.dart            # 底部操作栏
+│       ├── filter_tabs.dart           # Chip 筛选条
+│       ├── form_section.dart          # 表单分组
+│       ├── form_field_row.dart        # 表单行
+│       ├── section_card.dart          # 分区卡片
+│       ├── empty_state.dart           # 空状态
+│       ├── loading_widget.dart        # 骨架屏/菊花
+│       ├── approval_actions.dart      # 审批操作栏(经理版)
+│       ├── approval_timeline.dart     # 审批时间线
+│       ├── report_filter_bar.dart     # 报表筛选
+│       ├── message_item.dart          # 消息卡片
+│       ├── profile_menu_item.dart     # 个人中心菜单项
+│       └── pencil_nav_bar.dart        # 底部导航栏
+│
+├── features/
+│   ├── shell/
+│   │   ├── app_shell.dart             # Appshell(底部 Tab)
+│   │   └── nav_bar_config.dart
+│   ├── home/
+│   │   ├── home_page.dart             # 工作台
+│   │   └── home_controller.dart
+│   ├── messages/
+│   │   ├── message_list_page.dart
+│   │   ├── message_controller.dart
+│   │   └── message_model.dart
+│   ├── profile/
+│   │   └── profile_page.dart
+│   ├── expense_application/
+│   │   ├── expense_application_api.dart
+│   │   ├── expense_application_model.dart
+│   │   ├── expense_application_apply_page.dart
+│   │   ├── expense_application_list_page.dart
+│   │   ├── expense_application_list_controller.dart
+│   │   └── expense_application_detail_page.dart
+│   ├── expense/
+│   │   ├── expense_api.dart
+│   │   ├── expense_model.dart
+│   │   ├── expense_apply_page.dart
+│   │   ├── expense_list_page.dart
+│   │   ├── expense_list_controller.dart
+│   │   └── expense_detail_page.dart
+│   ├── overtime/
+│   │   ├── overtime_api.dart
+│   │   ├── overtime_model.dart
+│   │   ├── overtime_apply_page.dart
+│   │   ├── overtime_list_page.dart
+│   │   ├── overtime_list_controller.dart
+│   │   └── overtime_detail_page.dart
+│   ├── vehicle/
+│   │   ├── vehicle_api.dart
+│   │   ├── vehicle_model.dart
+│   │   ├── vehicle_apply_page.dart
+│   │   ├── vehicle_list_page.dart
+│   │   ├── vehicle_list_controller.dart
+│   │   └── vehicle_detail_page.dart
+│   ├── outing_log/
+│   │   ├── outing_log_api.dart
+│   │   ├── outing_log_model.dart
+│   │   ├── outing_log_create_page.dart
+│   │   ├── outing_log_list_page.dart
+│   │   ├── outing_log_list_controller.dart
+│   │   └── outing_log_detail_page.dart
+│   ├── announcement/
+│   │   ├── announcement_api.dart
+│   │   ├── announcement_model.dart
+│   │   ├── announcement_list_page.dart
+│   │   ├── announcement_list_controller.dart
+│   │   ├── announcement_detail_page.dart
+│   │   └── announcement_create_page.dart
+│   ├── report/
+│   │   ├── expense_apply_detail_report_page.dart
+│   │   ├── expense_detail_report_page.dart
+│   │   ├── overtime_detail_report_page.dart
+│   │   ├── vehicle_detail_report_page.dart
+│   │   └── outing_log_report_page.dart
+│   └── admin/
+│       └── admin_permissions_page.dart
+│
+└── .gitkeep 文件(各模块目录占位)
+```
+
+### 模块依赖关系
+
+```mermaid
+graph LR
+    subgraph features["features/"]
+        home["home"]
+        expense["expense"]
+        expense_apply["expense_application"]
+        overtime["overtime"]
+        vehicle["vehicle"]
+        outing_log["outing_log"]
+        announcement["announcement"]
+        report["report"]
+        admin["admin"]
+        messages["messages"]
+        profile["profile"]
+        shell["shell"]
+    end
+
+    subgraph shared["shared/"]
+        widgets["widgets"]
+        models["models"]
+    end
+
+    subgraph core["core/"]
+        router["router"]
+        network["network"]
+        auth["auth"]
+        theme["theme"]
+        utils["utils"]
+        i18n["i18n"]
+    end
+
+    home --> shared
+    expense --> shared
+    announcement --> shared
+    report --> shared
+    admin --> shared
+    shell --> router
+    shared --> core
+```
+
+所有 feature 模块依赖 `shared/` 和 `core/`。`shell` 依赖 `router` 管理 Tab 切换。模块之间不直接相互依赖,页面跳转通过 `GoRouter` 完成。
+
+---
+
+## 4. .NET 服务端模块划分
+
+```
+Tboss.OA.Server/
+├── Controllers/
+│   ├── OaPermissionController.cs      # 权限管理 API
+│   ├── OaExpenseApplyController.cs     # 事前申请 API
+│   ├── OaExpenseController.cs          # 费用报销 API
+│   ├── OaOvertimeController.cs         # 加班申请 API
+│   ├── OaVehicleController.cs          # 用车申请 API
+│   ├── OaOutingLogController.cs        # 外勤日志 API
+│   ├── OaAnnouncementController.cs     # 公告管理 API
+│   ├── OaReportController.cs          # 报表 API
+│   └── OaApprovalController.cs        # 审批代理 API
+│
+├── Services/
+│   ├── PermissionService.cs           # 权限校验 + 套餐管理
+│   ├── ExpenseApplyService.cs
+│   ├── ExpenseService.cs
+│   ├── OvertimeService.cs
+│   ├── VehicleService.cs
+│   ├── OutingLogService.cs
+│   ├── AnnouncementService.cs
+│   ├── ReportService.cs
+│   └── ApplicationNoGenerator.cs      # 单据编号原子生成
+│
+├── ErpIntegration/                     # ERP 集成适配器(统一模式,共 9 个)
+│   ├── IApprovalAdapter.cs            # 审批:创建实例/查询状态/执行动作/撤回
+│   ├── IBudgetAdapter.cs              # 预算:余额查询/冻结/扣减/释放
+│   ├── IProjectAdapter.cs             # 项目:列表/级联查询
+│   ├── ISubjectAdapter.cs             # 预算科目:按项目查科目列表
+│   ├── ICostCenterAdapter.cs          # 成本中心:列表查询
+│   ├── IStandardAdapter.cs            # 费用标准:按类型/城市/级别查上限
+│   ├── IExchangeRateAdapter.cs        # 汇率:币种→汇率查询
+│   ├── ICustomerAdapter.cs            # 客户:搜索/创建
+│   ├── ISupplierAdapter.cs            # 供应商:搜索(对公付款选供应商)
+│   ├── NullAdapters.cs                # 空实现集合(ERP 无能力时返回空/不限)
+│   └── ErpAdapterFactory.cs           # 按配置选取实现
+│
+├── Data/
+│   ├── Repositories/                  # Dapper 数据访问
+│   └── Sql/                           # SQL 脚本 / 迁移
+│
+├── Models/
+│   ├── Dto/                           # 请求/响应 DTO
+│   └── Entity/                        # 数据库实体映射
+│
+└── Infrastructure/
+    ├── ErpClient.cs                   # ERP HTTP 客户端封装
+    ├── MessageClient.cs               # 消息推送客户端
+    └── CacheManager.cs                # 报表数据缓存
+```
+
+---
+
+## 5. 核心接口清单
+
+### 5.1 OA 权限
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/api/oa/permissions` | 获取权限点列表 |
+| GET | `/api/oa/user-permissions?userId=` | 获取用户权限 |
+| PUT | `/api/oa/user-permissions` | 保存用户权限 |
+| GET | `/api/oa/permission-changelog?userId=&page=` | 权限变更审计日志 |
+
+### 5.2 业务单据
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| POST | `/api/oa/expense-apply/submit` | 提交事前申请 |
+| POST | `/api/oa/expense/submit` | 提交费用报销 |
+| POST | `/api/oa/overtime/submit` | 提交加班申请 |
+| POST | `/api/oa/vehicle/submit` | 提交用车申请 |
+| PUT | `/api/oa/{bizType}/draft` | 存草稿 |
+| GET | `/api/oa/{bizType}/list?status=&page=` | 列表查询 |
+| GET | `/api/oa/{bizType}/detail/:id` | 详情查询 |
+| DELETE | `/api/oa/{bizType}/:id` | 软删除单据 |
+
+### 5.3 外勤日志
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| PUT | `/api/oa/outing-log/submit` | 提交外勤日志 |
+| PUT | `/api/oa/outing-log/:id/view` | 标记已查看 |
+| POST | `/api/oa/outing-log/:id/comment` | 主管点评 |
+
+### 5.4 审批代理
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/api/oa/approval/pending?userId=&bizType=&page=` | 待审批列表 |
+| GET | `/api/oa/approval/pending-count?userId=` | 待审批数量 |
+| GET | `/api/oa/approval/timeline?bizType=&bizId=` | 审批时间线 |
+| POST | `/api/oa/approval/action` | 审批动作(同意/拒绝/转交) |
+| POST | `/api/oa/approval/withdraw` | 撤回申请 |
+| GET | `/api/oa/approval/my-history?userId=&page=` | 我的审批历史 |
+| GET | `/api/oa/approval/subordinates?approverId=&bizType=&page=` | 下属单据列表 |
+
+### 5.5 公告
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| POST | `/api/oa/announcement/publish` | 发布/更新公告 |
+| POST | `/api/oa/announcement/:id/read` | 标记已读 |
+| POST | `/api/oa/announcement/:id/ding` | DING 催办 |
+| GET | `/api/oa/announcement/read-stats/:id` | 已读/未读统计 |
+
+### 5.6 报表
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/api/oa/report/expense-apply?range=&userId=` | 事前申请报表 |
+| GET | `/api/oa/report/expense?range=&userId=` | 费用报销报表 |
+| GET | `/api/oa/report/overtime?range=&userId=` | 加班报表 |
+| GET | `/api/oa/report/vehicle?range=&userId=` | 用车报表 |
+| GET | `/api/oa/report/outing-log?range=&userId=` | 外勤日志报表 |
+| POST | `/api/oa/report/expense/export` | 导出费用报销 Excel |
+
+### 5.7 基础数据
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/api/oa/vehicles` | 车辆池列表 |
+| POST | `/api/oa/vehicles` | 添加车辆 |
+| PUT | `/api/oa/vehicles/:id` | 修改车辆 |
+| GET | `/api/oa/banners` | 轮播图列表 |
+
+### 5.8 复用已有 API
+
+| 方法 | URL | 说明 |
+|------|-----|------|
+| GET | `/api/user/{id}` | 用户信息 |
+| GET | `/api/user/search?q=&page=` | 用户搜索 |
+| GET | `/api/dept/tree` | 部门树 |
+| PUT | `/api/user/avatar` | 上传头像 |
+| GET | `/api/customer/search?q=` | 客户联想搜索 |
+| GET | `/api/messages?page=` | 消息列表 |
+| GET | `/api/messages/unread-count` | 未读消息数 |
+| PUT | `/api/messages/:id/read` | 标记已读 |
+| POST | `/api/messages/read-all` | 全部已读 |
+| DELETE | `/api/messages/:id` | 删除消息 |
+| GET | `/api/dict/banks` | 银行列表 |
+| GET | `/api/dict/cost-categories` | 费用类别 |
+
+---
+
+## 6. 第三方服务集成方案
+
+### 6.1 ERP 审批引擎
+
+```
+集成方式:.NET 服务端 HTTP 调用
+认证:ERP 内部 Token / Session(由 .NET 服务端维护)
+容错:单套 ERP 超时 15s,失败不影响其他 ERP 查询
+      重试 3 次,间隔 2s/4s/8s 递增
+```
+
+### 6.2 消息推送
+
+```
+集成方式:复用 .NET 服务端已有推送模块
+通道:App Push(原生通知栏)+ 站内消息(消息列表)
+触发点:审批创建/完成/拒绝/转交、公告发布、DING 催办
+OA 职责:调用 .NET 消息 API 构造消息体,推送执行由消息模块完成
+```
+
+### 6.3 原生能力(MethodChannel)
+
+| 能力 | 平台 | 用途 |
+|------|------|------|
+| 相机 | Android/iOS | 外勤拍照(防伪水印)、发票拍照、头像拍摄 |
+| 相册 | Android/iOS | 附件上传(图片/PDF/文件选择) |
+| GPS 定位 | Android/iOS | 外勤签到、用车始发地 |
+| 通讯录 | Android/iOS | 用车同行人选人、审批转交选人 |
+| 地图选点 | Android/iOS | 用车目的地经纬度选点 |
+| 文件分享 | Android/iOS | Excel 导出 → 系统分享面板 |
+| PDF 查看 | Android/iOS | 附件 PDF 原生预览 |
+
+### 6.4 支付
+
+```
+不涉及。OA 不做在线支付,财务核销是线下打款后录入电汇流水号和记账凭证号。
+```
+
+---
+
+## 7. 部署架构
+
+```mermaid
+graph TB
+    subgraph 用户设备["用户设备"]
+        direction LR
+        M1["Android App<br/>含 Flutter OA Module"]
+        M2["iOS App<br/>含 Flutter OA Module"]
+    end
+
+    subgraph 内网["公司内网"]
+        direction TB
+        S1["IIS / Windows Server<br/>.NET 4.8 服务端"]
+        DB1[("SQL Server 2019+<br/>OA 业务库")]
+
+        subgraph ERP群["ERP 系统群"]
+            ERP1["ERP-A<br/>审批 + 主数据"]
+            ERP2["ERP-B<br/>审批 + 主数据"]
+            ERP3["ERP-C<br/>审批 + 主数据"]
+        end
+    end
+
+    M1 -->|"HTTPS"| S1
+    M2 -->|"HTTPS"| S1
+    S1 --> DB1
+    S1 -->|"内网 HTTP"| ERP1
+    S1 -->|"内网 HTTP"| ERP2
+    S1 -->|"内网 HTTP"| ERP3
+
+    S1 -.->|"App Push<br/>FCM / APNs"| M1
+    S1 -.->|"App Push<br/>FCM / APNs"| M2
+```
+
+### 部署要点
+
+| 组件 | 部署位置 | 说明 |
+|------|---------|------|
+| Flutter OA Module | 嵌入 Android/iOS App | 随 App 发版更新 |
+| .NET 4.8 服务端 | Windows Server + IIS | 新增 OA Controller 部署到现有服务端 |
+| SQL Server | 现有数据库实例 | 新增 OA 库或扩展现有库(新建 OA 表) |
+| ERP 系统 | 内网已有 | OA 通过内网 HTTP 调用 |
+| 推送服务 | FCM (Android) / APNs (iOS) | 已有消息模块管理,OA 不直接对接 |
+
+### 配置项
+
+```xml
+<!-- .NET 服务端 web.config 新增 -->
+<appSettings>
+  <!-- ERP 适配器配置:每项可设为 ErpA/ErpB/Null/OaLocal -->
+  <add key="Oa:Adapter:Approval" value="ErpA" />
+  <add key="Oa:Adapter:Budget" value="ErpA" />
+  <add key="Oa:Adapter:Project" value="ErpA" />
+  <add key="Oa:Adapter:Subject" value="ErpA" />
+  <add key="Oa:Adapter:CostCenter" value="ErpA" />
+  <add key="Oa:Adapter:Standard" value="Null" />       <!-- ERP 暂未维护费用标准 -->
+  <add key="Oa:Adapter:ExchangeRate" value="ErpA" />
+  <add key="Oa:Adapter:Customer" value="ErpA" />
+  <add key="Oa:Adapter:Supplier" value="Null" />       <!-- 对公供应商暂自由文本 -->
+  
+  <!-- ERP API 地址 -->
+  <add key="ErpA:BaseUrl" value="http://erp-a.internal/api/" />
+  <add key="ErpB:BaseUrl" value="http://erp-b.internal/api/" />
+  
+  <!-- 调用超时(毫秒) -->
+  <add key="Oa:ErpTimeout" value="15000" />
+  
+  <!-- 报表缓存时间(分钟) -->
+  <add key="Oa:ReportCacheMinutes" value="5" />
+</appSettings>
+```
+
+---
+
+## 8. 安全设计
+
+| 维度 | 方案 |
+|------|------|
+| 传输安全 | HTTPS(移动端 → 服务端);内网 HTTP(服务端 → ERP) |
+| 认证 | 复用宿主 App 的登录态;401 → 触发宿主重新登录 |
+| 授权 | OA 端:OaUserPermission ACL;服务端:每个 API 校验权限点 |
+| 数据隔离 | 列表查询按 `view_own/view_dept/view_all` 权限控制数据范围 |
+| SQL 注入 | Dapper 参数化查询 |
+| 审计 | OaPermissionChangeLog 记录所有权限变更 |
+| 软删除 | 所有业务表 IsDeleted=1,禁止物理删除 |
+
+---
+
+## 9. ERP 适配器接口定义
+
+### 9.1 IApprovalAdapter
+
+```csharp
+public interface IApprovalAdapter
+{
+    // 创建审批实例,返回 ERP 实例 ID
+    Task<string> SubmitAsync(string bizType, long bizId, object formData, List<string> tags = null);
+    // tags: ["budget_exceeded", "standard_exceeded"] → ERP 自动插入特批节点
+
+    // 查询审批状态
+    Task<ApprovalStatus> GetStatusAsync(string instanceId);
+
+    // 查询审批时间线
+    Task<List<TimelineNode>> GetTimelineAsync(string bizType, long bizId, string instanceId);
+
+    // 执行审批动作
+    Task<ActionResult> ExecuteActionAsync(string instanceId, string action, string opinion, long? transferToUserId);
+
+    // 撤回
+    Task WithdrawAsync(string instanceId);
+
+    // 查询待办列表
+    Task<PendingList> GetPendingListAsync(long approverId, string bizType, int page, int pageSize);
+}
+```
+
+### 9.2 IBudgetAdapter
+
+```csharp
+public interface IBudgetAdapter
+{
+    // 查余额(含冻结金额)
+    Task<BudgetBalance> GetBalanceAsync(long projectId, long subjectId);
+
+    // 冻结(提交时),返回新 Version
+    Task<long> FreezeAsync(long projectId, long subjectId, decimal amount);
+
+    // 扣减(审批通过时),携带 Version 乐观锁
+    Task DeductAsync(long projectId, long subjectId, decimal amount, long version);
+
+    // 释放(拒绝/撤回时)
+    Task ReleaseAsync(long projectId, long subjectId, decimal amount);
+}
+
+public class BudgetBalance
+{
+    public bool HasBudget { get; set; }           // false → OA 前端隐藏预算区块
+    public decimal AllocatedAmount { get; set; }
+    public decimal AvailableAmount { get; set; }
+    public decimal FrozenAmount { get; set; }
+    public decimal ActualAvailable => AvailableAmount - FrozenAmount;
+}
+```
+
+### 9.3 IProjectAdapter / ISubjectAdapter / ICostCenterAdapter
+
+```csharp
+public interface IProjectAdapter
+{
+    Task<List<IdName>> GetProjectsAsync();                    // 项目列表
+    Task<List<IdName>> GetSubjectsAsync(long projectId);      // 某项目下的预算科目
+}
+
+public interface ISubjectAdapter
+{
+    Task<List<IdName>> GetSubjectsAsync(long? projectId);
+}
+
+public interface ICostCenterAdapter
+{
+    Task<List<IdName>> GetCostCentersAsync();
+}
+
+public class IdName { public long Id; public string Name; }
+```
+
+### 9.4 IStandardAdapter
+
+```csharp
+public interface IStandardAdapter
+{
+    Task<StandardCheckResult> CheckAsync(string expenseType, string cityLevel, string employeeLevel, decimal amount);
+}
+
+public class StandardCheckResult
+{
+    public bool HasStandard { get; set; }     // false → OA 不校验
+    public bool Pass { get; set; }
+    public decimal MaxAmount { get; set; }
+    public decimal ExceedAmount { get; set; }
+}
+```
+
+### 9.5 IExchangeRateAdapter / ICustomerAdapter / ISupplierAdapter
+
+```csharp
+public interface IExchangeRateAdapter
+{
+    Task<ExchangeRate> GetRateAsync(string currencyCode);
+    // { CurrencyCode: "USD", Rate: 7.2456, Date: "2026-06-03" }
+}
+
+public interface ICustomerAdapter
+{
+    Task<List<IdName>> SearchAsync(string keyword);
+    Task<long> CreateAsync(string customerName);  // 外勤日志新客户自动创建
+}
+
+public interface ISupplierAdapter
+{
+    Task<List<IdName>> SearchAsync(string keyword);
+}
+```
+
+### 9.6 NullAdapter 模式
+
+```csharp
+// ERP 无某能力时,使用 Null 实现——OA 前端自动隐藏对应区块
+public class NullBudgetAdapter : IBudgetAdapter
+{
+    public Task<BudgetBalance> GetBalanceAsync(long p, long s)
+        => Task.FromResult(new BudgetBalance { HasBudget = false });
+    public Task<long> FreezeAsync(long p, long s, decimal a) => Task.FromResult(0L);
+    public Task DeductAsync(long p, long s, decimal a, long v) => Task.CompletedTask;
+    public Task ReleaseAsync(long p, long s, decimal a) => Task.CompletedTask;
+}
+```
+
+
+## 10. 事前申请→报销:适配器调用链
+
+### 10.1 事前申请提交流程
+
+```mermaid
+sequenceDiagram
+    participant OA as Flutter OA
+    participant NET as .NET 服务端
+    participant P as ProjectService
+    participant S as SubjectService
+    participant B as BudgetService
+    participant ST as StandardService
+    participant A as ApprovalService
+
+    OA->>NET: 打开表单
+    NET->>P: 项目列表
+    P-->>NET: [{id, name}]
+    NET->>S: 某项目下科目
+    S-->>NET: [{id, name}]
+    NET-->>OA: 级联数据
+
+    OA->>NET: 选完项目+科目
+    NET->>B: 查可用余额
+    B-->>NET: { hasBudget, available, frozen }
+    NET-->>OA: 预算余额 ¥XX
+
+    OA->>NET: 提交
+    NET->>ST: 校验费用标准
+    ST-->>NET: { pass, exceedAmount }
+    NET->>B: 冻结预算
+    B-->>NET: { version }
+    NET->>A: 创建审批实例(含 tags)
+    A-->>NET: { instanceId }
+    NET-->>OA: { approvalInstanceId, status }
+```
+
+### 10.2 费用报销提交流程
+
+```mermaid
+sequenceDiagram
+    participant OA as Flutter OA
+    participant NET as .NET 服务端
+    participant C as CostCenterService
+    participant E as ExchangeRateService
+    participant ST as StandardService
+    participant B as BudgetService
+    participant A as ApprovalService
+
+    OA->>NET: 打开表单
+    NET->>C: 成本中心列表
+    C-->>NET: [{id, name}]
+    NET-->>OA: 下拉数据
+
+    OA->>NET: 明细行选外币 USD
+    NET->>E: 查 USD 汇率
+    E-->>NET: { rate: 7.2456 }
+    NET-->>OA: 本币金额 = 原币 × 7.2456
+
+    OA->>NET: 提交
+    NET->>ST: 逐行校验费用标准
+    ST-->>NET: [{ line, pass, exceedAmount }]
+    alt 有超标行
+        NET-->>OA: 超标行标红,tags=["standard_exceeded"]
+    end
+    NET->>OA: 查未还借款 → 弹窗确认冲销
+    OA->>NET: 确认冲销明细
+    NET->>B: 扣减预算
+    B-->>NET: ok
+    NET->>A: 创建审批实例 { tags: [...] }
+    A-->>NET: { instanceId }
+    NET->>NET: 写 ExpenseApplicationMapping + LoanRepayment
+    NET-->>OA: { instanceId, status }
+```
+
+### 10.3 适配器覆盖矩阵
+
+| 环节 | Approval | Budget | Project | Subject | CostCenter | Standard | ExchRate | Customer | Supplier |
+|------|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
+| 事前-填单 | | 余额 | ✅ | ✅ | | | | | |
+| 事前-提交 | ✅ 创建 | ✅ 冻结 | | | | ✅ 校验 | | | |
+| 事前-审批 | ✅ 流转 | | | | | | | | |
+| 事前-通过 | | ✅ 扣减 | | | | | | | |
+| 事前-变更 | ✅ 补充 | ✅ 追加冻结 | | | | ✅ | | | |
+| 报销-填单 | | | | | ✅ | | ✅ | | ✅ |
+| 报销-提交 | ✅ 创建 | ✅ 扣减 | | | | ✅ 校验 | | | |
+| 报销-审批 | ✅ 流转 | | | | | | | | |
+| 报销-撤回 | ✅ 撤回 | ✅ 释放 | | | | | | | |
+
+
+## 11. ERP 能力缺位时的 OA 自治扩展
+
+当 ERP 暂无某项能力,后续 OA 需要自建时,扩展路径标准化:
+
+### 11.1 三步扩展法
+
+```
+步骤 1: 建表
+  ERP 无预算 → OA 新建 SysProjectBudget 表(DDL 已就绪)
+  ERP 无标准 → OA 新建 ExpenseStandard 表 + 管理页
+
+步骤 2: 写实现
+  新建 OaLocalBudgetAdapter : IBudgetAdapter
+  新建 OaLocalStandardAdapter : IStandardAdapter
+
+步骤 3: 切配置
+  appSettings: Oa:Adapter:Budget 从 Null 切为 OaLocal
+  重启 .NET 服务端生效
+```
+
+### 11.2 影响范围
+
+| 层面 | 改动 |
+|------|------|
+| 数据库 | +1 张表(迁移脚本) |
+| .NET 服务端 | +1 个 Adapter 实现类(~50 行) |
+| web.config | 改 1 行配置 |
+| Flutter 前端 | **零改动**(API 契约不变) |
+| OA API | **零改动** |
+
+### 11.3 预设可用性
+
+| 能力 | 当前 | NullAdapter 行为 | 如需自建 |
+|------|------|-----------------|---------|
+| 预算 | 取决于 ERP | hasBudget=false → OA 隐藏余额 | +SysProjectBudget 表 |
+| 费用标准 | 取决于 ERP | hasStandard=false → 不校验超标 | +ExpenseStandard 表+管理页 |
+| 汇率 | 取决于 ERP | 只返回 CNY (rate=1) | +汇率表+维护页 |
+| 项目 | 始终需要 | 空列表 → 无下拉选项 | +SysProject 管理页 |
+| 成本中心 | 可空 | 空列表 → 可空不强制选 | +SysCostCenter 管理页 |
+| 供应商 | 可空 | 空列表 → 自由文本输入 | +SysSupplier 管理页 |
+
+
+---
+
+> **文档版本**:v1.0 | 日期:2026-06-03

File diff suppressed because it is too large
+ 1319 - 0
docs/superpowers/specs/tboss-oa-database.md


+ 598 - 0
docs/superpowers/specs/tboss-oa-design.md

@@ -0,0 +1,598 @@
+# TBOSS OA 模块 — 全量 UI 设计
+
+> 版本:v1.0 | 日期:2026-06-03
+> 基于 `tboss-oa-product-strategy.md` / `tboss-oa-prd.md` / `tboss-oa-architecture.md` / `tboss-oa-database.md` / `tboss-oa-api.md`
+>
+> **架构前提**:用户/组织/客户数据来自 ERP(通过 .NET API)、审批引擎复用 ERP、消息复用 .NET 服务端、OA 权限独立 ACL。
+
+---
+
+## 一、普通员工 (Employee) 角色视图
+
+普通员工拥有 26 个基础页面的完整闭环:发起申请、查看个人流水、编辑草稿、撤回。
+
+### 1. 框架与导航页(4 页)
+
+#### 页面 1:Appshell 底部导航 (`/`)
+
+**页面组件内容**:
+- 底部 `TDTabBar`:消息、工作台、我的
+- 主内容区:`GoRouter` + `IndexedStack` 视图容器
+
+**微观组件交互**:
+- 点击 Tab → 路由切换,图标变 `#00ABF3`
+- 重复点击当前 Tab → 列表页 ScrollToTop,非列表无行为
+- 消息 Tab 角标:未读消息数,进入时拉取 + 30s 轮询或宿主 Push 刷新,数据来源 .NET 消息模块 API
+
+---
+
+#### 页面 2:消息通知聚合页 (`/messages`)
+
+**页面组件内容**:
+- `TDNavbar` 标题"消息通知",右侧"全部已读"按钮(仅未读>0 时显示)
+- `TDListView`:消息图标(审批待办/审批结果/系统公告)、标题、发送人、发送时间(MM-DD HH:mm)、摘要文本、未读红点 `TDBadge`
+
+**微观组件交互**:
+- 左滑卡片 → `[标记已读]`(蓝) + `[删除]`(红);已读消息不可左滑(无操作按钮)
+- `[标记已读]` → 调 .NET 消息 API → 成功则红点淡出+透明度↓0.6;失败 Toast"操作失败,请重试"
+- `[删除]` → TDDialog"确认删除?删除后不可恢复。"→ 确认则卡片收起+调 .NET 消息 API 删除
+- 下拉刷新(page=1)/ 上拉加载(page++)
+- 点击"公告通知"卡片 → `/announcement/detail/:id`
+- 点击"审批待办"卡片 → 按 `bizType` 路由至对应详情页,底部展审批操作栏
+- 点击"审批结果"卡片 → 按 `bizType` 路由至对应详情页
+
+**数据来源**:.NET 消息模块 API(非本地 Message 表)
+
+---
+
+#### 页面 3:工作台 (`/home`)
+
+**角色判定**(决定工作台变体,查 `OaUserPermission`):
+- 有 `oa.admin.*` → 管理员版
+- 无 admin 但有 `oa.expense.mark_paid` → 财务版
+- 无上述但有 `oa.*.approve` 或 `oa.*.view_dept` → 经理版
+- 其余 → 员工版
+
+**员工版组件**:
+- `TDRotation` 轮播图(3s 切换,点击有 LinkUrl→跳转,无→全屏预览双指缩放)。数据来源 `SysBanner`(OA 本地)
+- 金刚区(TDGrid 4 列):
+  - 第一行(发起):事前申请→`/expense-apply/apply`、费用报销→`/expense/apply`、用车申请→`/vehicle/apply`、加班申请→`/overtime/apply`
+  - 第二行(记录):申请记录→`/expense-apply/list`、报销记录→`/expense/list`、外勤日志→`/outing-log/list`、公司公告→`/announcement/list`
+- 快捷看板:本月累计报销(大字)、本月已提单据总数、待处理单据总数。数据由 .NET 服务端预计算(缓存 5min),下拉强制穿透缓存
+  - 点击"已提单据"→`/expense-apply/list`
+  - 点击"本月报销"→`/report/expense-detail`
+  - 点击"待处理"→`/messages`(筛选 approval_notice)
+
+**经理版增量**:
+- 金刚区上方插【待我处理的审批】卡片,红色角标显示待办数(`GET /api/oa/approval/pending-count`),点击→`/messages`(筛选 approval_notice)
+
+**财务版增量**:
+- 顶部替换为全公司财务看盘(已支付流水/待付款总额/异常退回数),数据来源 .NET 服务端全公司聚合查询
+
+---
+
+#### 页面 3.1:个人中心 (`/profile`)
+
+**页面组件内容**:
+- 头像(TDAvatar,点击更换)、姓名、部门、岗位。数据来源 `GET /api/user/{erpUserId}`
+- 功能列表:我的审批历史、我的报表、关于 TBOSS OA
+
+**微观组件交互**:
+- 头像 → 唤起相册/相机 → 裁剪(1:1,≤2MB) → `PUT /api/user/avatar` → 全局刷新;失败 Toast"头像上传失败"
+- 登录态:宿主 App 统一管理。OA 监听宿主登出事件→清本地缓存;API 401→MethodChannel 通知宿主重登录
+
+**子页面:我的审批历史**
+- 按时间倒序展示本人发起的所有单据审批流水,按单据分组
+- 每组卡片:单据编号(可点击跳详情页)、类型标签(事前申请/报销/加班/用车)、审批时间线摘要
+- 数据来源:`GET /api/oa/approval/my-history?userId={erpUserId}&page=1`(.NET 服务端汇总各 ERP 审批记录)
+- 下拉刷新 / 上拉加载;空数据 TDEmpty"暂无审批记录"
+
+**子页面:我的报表聚合页**
+- 顶部 TDTabBar 五 Tab:事前申请/费用报销/加班明细/用车明细/外勤日志
+- 切换渲染页面 21~25 个人报表视图
+
+---
+
+### 2. 费用控制与报销模块(10 页)
+
+#### 页面 4.0:借款/备用金表单 (`/loan/apply`)
+
+**页面组件内容**:
+- 借款类型 TDPicker(备用金/差旅借款)
+- 借款金额 TDInput(纯数字)
+- 借款事由 TDTextarea
+- 同其他审批表单:存草稿+提交审批,调 `POST /api/oa/loan/submit`
+
+#### 页面 4.0a:借款列表 (`/loan/list`)
+
+同标准列表:TDChip(全部/草稿/审批中/已通过/已拒绝/已撤回)+ 还款状态标签(未还/部分已还/已还清)
+
+#### 页面 4.0b:借款详情 (`/loan/detail/:id`)
+
+展示借款信息+审批时间线。已通过且未还清的借款展示还款记录列表。
+
+---
+
+
+#### 页面 4:事前申请表单 (`/expense-apply/apply`)
+
+**页面组件内容**:
+- `FormSection` 基本信息:申请人姓名(只读,`GET /api/user/{erpUserId}`)、所属部门(只读,同上)、申请日期(只读,`DateTime.now()`)、紧急程度(TDRadioGroup:普通/紧急/特急)、费用类型(TDCheckboxGroup 多选,至少一项,如"差旅费"+"办公费")、费用事由(TDTextarea,≤200 字)
+- `FormSection` 关联管控:关联项目(TDPicker 级联,.NET → ERP ProjectService)、预算科目(.NET → ERP SubjectService)、可用余额(.NET → ERP BudgetService,ERP 无预算则隐藏此区块)
+- `FormSection` 动态预估明细:[+ 添加费用明细],每项含费用类别、预估金额(TDInput 纯数字)、明细说明
+- 提交时 .NET 调 ERP StandardService 校验费用标准,超标行标红+触发特批
+- `FormSection` 支撑材料上传:TDImageGrid 最大 6 张,图片≤10MB/PDF≤20MB。点击[+]唤起相册/相机/文件;满 6 隐藏[+];点击缩略图→黑底全屏预览(双指缩放 0.5x~3x);PDF→原生查看器;缩略图⊖→气泡确认删除。存入 `Attachment (BizType='expense_apply')`
+- 底部操作栏:编辑草稿时 `[重置]`+`[存为草稿]`+`[提交审批]`;新建时仅后两者
+
+**微观组件交互**:
+- `[重置]`→确认弹窗→清空至初始状态
+- 预算余额展示:项目+科目选定后异步加载"当前可用预算余额:¥XXXX.xx"。数据来源 .NET 转发 ERP(或 OA 自建 SysProjectBudget)
+- 预估金额合计 > 可用余额 → 汇总区变红 + 警告"您的申请金额已超支,提交后将自动触发高管特批流程"
+- 明细 [+]/✕ 增删动画
+- 返回(有未保存修改)→TDDialog"是否退出?"
+
+**页间流转**:
+- `[存为草稿]` → Loading → `PUT /api/oa/expense-apply/draft` → Toast 绿"已保存为草稿" → 跳列表(激活"草稿"Chip);失败 Toast 红
+- `[提交审批]` → 全局校验 → ScrollTo 报错字段(300ms)+红闪+TDMessage → Loading → `POST /api/oa/expense-apply/submit` → .NET 服务端创建 ERP 审批实例 → 返回 ApprovalInstanceId → 存 OA → Toast"已提交,等待审批" → 跳列表
+
+---
+
+#### 页面 5:费用报销表单 (`/expense/apply`)
+
+**页面组件内容**:
+- 顶部【导入已通过的事前申请】蓝色高亮快捷链。支持导入多张申请,每张填导入金额
+- 对公/对私切换 TDSwitch:对私(默认)→ 收款人账户;对公 → 供应商名称+对公账户
+- 基本信息:报销事由、关联成本中心(TDPicker,.NET → ERP CostCenterService,可空)、报销总金额(只读,明细累加上浮动画)。项目和科目从导入的申请自动带入
+- 收款账户(对私):开户行(TDInput+下拉联想,`GET /api/dict/banks`)、户名(默认当前用户)、账号(校验 16-19 位)
+- 供应商(对公):供应商名称 TDInput、对公银行账户
+- 动态报销明细:发生日期、费用类别(TDPicker,`SysCostCategory`)、币种(TDPicker,默认 CNY,选外币自动从 ERP 填汇率)、原币金额→自动算本币、发票类型/号码/代码/税率。标准校验超标行标红+触发特批
+- 费用分摊:明细行可指定分摊比例+目标部门/项目(如 60%→DeptA,40%→DeptB),不分摊时留空
+- 发票上传:TDImageGrid 最大 9 张,≤10MB。存入 `Attachment (BizType='expense')`,可绑定明细行
+- 若员工有未还借款,提交时自动匹配冲销:报销金额先抵借款,余款退/补
+
+**微观组件交互**:
+- [导入事前申请] → 半屏抽屉多选:罗列 approved 且尚有额度的申请,每条填导入金额。空→TDEmpty。可追加多条,已选列表可移除
+- 借款匹配:提交时 .NET 自动查未还借款 → 弹窗展示冲销明细("报销 ¥8000 → 抵借款 ¥5000 → 实退 ¥3000"),用户确认后提交
+- 发票金额输入→底部总金额上浮动画累加
+- 明细增删、附件预览同页面 4
+- 银行账号前端校验 16-19 位
+
+**页间流转**:同页面 4,API 为 `POST /api/oa/expense/submit`
+
+---
+
+#### 页面 6/7:事前申请/费用报销列表页
+
+**页面组件内容**:
+- TDNavbar 标题"申请记录"/"报销记录"
+- TDChip 筛选:全部/草稿/审批中/已通过/已拒绝/已撤回
+- TDListView 流水卡片:单据编号、事由摘要、金额(粗体高亮)、提单日期、StatusTag(灰-草稿/橙-审批中/绿-已通过/红-已拒绝/灰-已撤回)
+
+**微观组件交互**:
+- 首次加载默认"全部"Chip
+- Chip 切换→高亮 `#00ABF3`→列表淡入淡出刷新;无数据→TDEmpty(文案随 Chip 变化)
+- 下拉刷新(page=1)/ 上拉加载(page++)。列表数据来源 OA 本地 `ApprovalStatus` 缓存,后台静默从 .NET→ERP 刷新
+- draft/rejected 卡片左滑→`[编辑]`(蓝)+`[删除]`(红);其他状态左滑无反应
+- `[删除]`→TDDialog"确认删除?不可恢复。"→卡片收起+软删除
+- 点击卡片→对应详情页
+
+**经理版增量**:
+- 导航栏下方 `[我的发起]` / `[下属审批]` 范围标签
+- 切换到 `[下属审批]`→骨架屏→加载部门下属单据(`GET /api/oa/approval/subordinates`),卡片追加申请人姓名-部门
+- pending 下属卡片左滑→`[一键同意]`(绿),就地审批(`POST /api/oa/approval/action`)
+
+**财务版增量(仅页面 7)**:
+- 独家 `[待付款]` Chip(筛选 Status='approved' AND PaymentStatus='unpaid')
+- 卡片左滑→`[线下已付款]`,标记 PaymentStatus='paid'
+
+---
+
+#### 页面 8/9:事前申请/费用报销详情页
+
+**页面组件内容**:
+- 状态大色块横幅:图标(⏳/✅/❌/↩)+状态中文+当前审批人姓名。已撤回底灰。审批人姓名从 .NET API 获取
+- 提交时间(CreateTime,`--fs-caption` 字号)
+- 单据信息+费用明细表(key-value 双栏;>5 行折叠+展开按钮)
+- 附件宫格(页面 8:支撑材料;页面 9:发票影像)
+- 审批时间线:`GET /api/oa/approval/timeline?bizType=&bizId=` → .NET → ERP。首节点"发起人已提交"(拼装 CreateTime+申请人)。若 withdrawn→末尾追加撤回节点
+- 底部悬浮操作栏(按状态动态变化)
+
+**微观组件交互**:
+- draft 底部:`[编辑]`+`[提交审批]`;提交校验失败→TDDialog"去编辑"/"取消"
+- pending 底部:`[撤回申请]`→TDDialog 确认 → `POST /api/oa/approval/withdraw` → ApprovalStatus='withdrawn',旧 instanceId 追加到 PreviousInstanceIds → 页面刷新
+- rejected 底部:`[重新编辑并发起]`→跳表单编辑态,提交后更新原记录
+- approved/withdrawn 底部:隐藏
+- **申请变更/追加**(approved 状态):详情页右上角 `[变更申请]` 按钮 → 弹出变更类型选择(追加金额/减少金额/新增明细/删除明细/延期)→ 填写变更事由 → 提交触发补充审批(存入 ExpenseApplicationChange,ApprovalInstanceId 关联 ERP 补充审批)。审批通过后 .NET 自动将 AfterSnapshot 写回主表和明细表
+- 时间线节点点击:当前用户待审批→弹出审批操作(经理版操作栏);否则→展开详情
+- 审批人姓名/头像点击 → TDDialog 展示基本信息卡片(调 `GET /api/user/{approverId}`)
+
+**经理版底部操作栏**(pending 且当前审批人=该经理时展示):
+
+```
+┌──────────────────────────────────────────────────────────┐
+│ [拒绝] (必填理由)  │ [转交] (调原生通讯录) │ [同意] (下流转) │
+└──────────────────────────────────────────────────────────┘
+```
+
+- `[拒绝]`→TDDialog 输入理由,≥5 字解锁确定按钮 → `POST /api/oa/approval/action` {action:reject}
+- `[转交]`→MethodChannel 通讯录单选 → `POST /api/oa/approval/action` {action:transfer, transferToUserId}
+- `[同意]`→Loading → `POST /api/oa/approval/action` {action:approve} → 节点变绿流转
+- 风控参考看板:申请人本月同类单据数 + 预算消耗进度条(本地查询+ .NET 转发 ERP)
+
+**管理员版底部操作栏**:
+- `[强制终止]`(红底白字)+ `[查看审批链]`(含历史 instanceId 流程)。管理员不展示同意/拒绝/转交
+
+**财务版增量(仅页面 9)**:
+- 发票合规查验区:三个 TDCheckbox(发票已验真/税号一致/类目合规)。全部勾选→解锁打款按钮
+- `[退回修改]`→选退回节点(员工重填/经理重审)→状态倒流
+- `[确认已线下打款并归档]`→解锁后滑出凭证表单(电汇流水号+记账凭证号必填)→提交→PaymentStatus='paid'→归档
+- 归档后自动替换为 `[下一笔待付款]`→跳转下一笔 approved+unpaid;无→"已无待付款单据"+Toast
+
+---
+
+### 3. 勤务考勤、用车调度与外勤外务模块(9 页)
+
+#### 页面 10:加班申请表单 (`/overtime/apply`)
+
+**页面组件内容**:
+- 加班类型 TDPicker(工作日/休息日/法定节假日,默认"工作日");节假日标注费率
+- 补偿方式 TDRadio(转调休/结算加班费/混合,默认"转调休")
+- 混合模式 TDSlider(10%~90%,步长 10%,右侧实时比例文案)
+- 开始/结束时间 TDDatePicker(年月日时分)
+- 净工时大字卡片(只读)
+- 加班原因 TDTextarea
+
+**微观组件交互**:
+- 结束时间确认→自动计算净工时(扣除 12:00-13:00/18:00-18:30 盲区),净工时≤0→变红+按钮置灰
+- 开始>结束→边框变红+提示+按钮置灰
+- 混合模式滑块:右侧实时"30% 转调休 + 70% 结算加班费"
+- 存草稿/提交审批复用页面 4 逻辑。提交调 `POST /api/oa/overtime/submit`
+
+---
+
+#### 页面 11/12:加班列表/详情
+
+交互同页面 6/7 和 8/9。加班类型标签(工作日/休息日/节假日)、补偿方式展示(加班费/转调休/"X%调休+Y%结算")。员工端仅 `[撤回]`。
+
+---
+
+#### 页面 13:用车申请表单 (`/vehicle/apply`)
+
+**页面组件内容**:
+- 车牌号 TDPicker(`SysVehicle` 车池,冲突车辆标红"冲突";无可用→TDEmpty)
+- 用车事由 TDInput
+- 始发地(原生定位匹配)+ 目的地(TDInput + 地图图标→MethodChannel 地图选点回填地址和经纬度)
+- 出车/还车时间 TDDatePicker
+- 同行总人数(纯数字,0→1)+ 随行同行人 [+](MethodChannel 通讯录多选→胶囊标签展示,x 剔除)
+- 存草稿/提交审批复用页面 4 逻辑。提交调 `POST /api/oa/vehicle/submit`
+
+**微观组件交互**:
+- 车牌+时间选定→异步排期冲突检测。冲突→红色"注意:当前时段该车存在排期冲突!"+锁死提交按钮
+- 还车时间>出车时间校验
+
+---
+
+#### 页面 14/15:用车列表/详情
+
+**列表增量**:
+- TDChip 含"已还车"(`returned` 状态)
+- approved 卡片左滑→`[确认还车]`,拉起核销抽屉
+- 已还车卡片左滑→无操作
+
+**详情-还车登记**:
+- approved 时底部展 `[确认还车并登记]`(提前还车标灰字提示;超时标红字提示)
+- 点击→半屏核销抽屉:实还时间(TDDatePicker)、出车前里程(TDInput 纯数字)、还车后里程(校验≥出车前+红色提示)、路桥停车费备注
+- 确认提交→TDDialog"提交后里程和费用不可再修改。"→Status='returned'→页面刷新+底部灰色"已还车归档于 YYYY-MM-DD HH:mm"
+
+---
+
+#### 页面 16:外勤日志创建 (`/outing-log/create`)
+
+**页面组件内容**:
+- GPS 定位区:页面初始化强制高精度 GPS → ReadOnly 展示逆地理编码地址(绿色盾牌安全图标)。精度>100m→黄色警告
+- 客户名称 TDInput(输入联想 `GET /api/customer/search?q=`;无匹配→自由文本,提交时 .NET 自动创建 ERP 客户)
+- 今日工作总结 TDTextarea
+- 后续推进计划 TDInput
+- 现场强制拍照:跳过相册,直接唤起相机;水印=服务器授时+GPS;最少 1 张最多 4 张
+
+**微观组件交互**:
+- GPS 失败/被拒→TDEmpty"无法获取当前位置"+提交按钮置灰
+- 相机权限被拒→TDDialog + "前往设置"
+- 照片<1→提交按钮置灰+红色提示;满 4 隐藏拍照入口
+- `[存为草稿]`+`[提交]`,提交调 `PUT /api/oa/outing-log/submit`
+
+---
+
+#### 页面 17/18:外勤日志列表/详情
+
+**列表**:
+- TDChip(全部/本月)
+- 卡片:拜访编号(VST-YYYYMMDD-XXX)、客户名(粗体)、拜访日期、签到地址(截断一行)、工作摘要(截取 50 字)、StatusTag。若有新点评→橙色 TDBadge"新点评"
+- 新点评判断:`OutingLogComment.CreateTime > COALESCE(LastViewedTime,'1900-01-01')` 且评论者≠本人
+- draft 卡片左滑→`[编辑]`+`[删除]`;completed 左滑无反应
+
+**详情**:
+- 微缩静态地图(点击→MethodChannel 唤醒原生导航)
+- 带防伪水印照片墙(点击全屏预览)
+- 工作总结汇报正文
+- 点评历史流水区(OutingLogComment,按时间升序气泡样式)
+- 进入详情→无感 `PUT /api/oa/outing-log/:id/view`,更新 LastViewedTime,列表红点消失
+
+**经理版增量**:
+- 底部【主管批示及打分专区】:TDRate 5 星 + 文字输入框 + 发送按钮
+- 点选星级+输入点评→发送→气泡追加+动画。写入 `OutingLogComment`,同步更新 `OutingLog.UpdateTime`
+
+---
+
+### 4. 行政公告与报表模块(7 页)
+
+#### 页面 19:公告列表 (`/announcement/list`)
+
+**组件**:
+- TDChip 类型筛选:全部/通知公告/人事与制度/放假与活动/我的草稿(仅管理员可见)
+- 排序:置顶→未过期(PublishTime DESC)→已过期(PublishTime DESC)
+- 卡片:标题(粗体)、类型标签、发布部门(调 `GET /api/user/{publisherId}`→DeptName)、发布时间(MM-DD HH:mm)、未读红点(`AnnouncementReadLog.IsRead=0`)
+- 已过期卡片整体置灰+标题末尾"已过期"
+- 不支持搜索
+
+---
+
+#### 页面 20:公告详情 (`/announcement/detail/:id`)
+
+**组件**:
+- 红头文件样式:大标题、发布部门、红头线、正文 HTML/Markdown 渲染
+- 已过期→红色横幅"该公告已于 YYYY-MM-DD HH:mm 过期"
+- 附件下载专区(`Attachment (BizType='announcement')`);空则整区隐藏。点击→原生浏览器/下载管理器;失败→Toast
+
+**交互**:
+- 停留≥2s→无感 `POST /api/oa/announcement/:id/read`;<2s 返回不发。返回列表后红点消失
+
+**管理员版增量**:
+- 【全员触达率审计追踪卡片】:`[已读 N 人]`(绿)+`[未读 N 人]`(灰)。点击展开员工列表(头像+部门,数据来源 `AnnouncementReadLog` + `GET /api/user/{id}`)
+- `[一键 DING]`→震动反馈→封装未读 UserIds→MethodChannel 强推 Push/短信→Toast"已向 N 名未读员工发送催办通知"
+
+---
+
+#### 页面 21~25:五大明细报表
+
+统一框架:
+- TDDropdownMenu 筛选(本月/本季/本年),不支持自定义日期
+- 数值卡片(只读,空→0/¥0.00)
+- fl_chart 趋势图(长按 tooltip、水平滑动)
+- 切换维度→300ms 渐变刷新
+- 空数据→TDEmpty"所选时间范围内暂无数据"
+
+各报表内容:
+
+| 页面 | 报表 | 数值卡片 | 趋势图 |
+|------|------|---------|--------|
+| 21 | 事前申请明细 | 累计申请总额/本月笔数/已通过笔数/已通过金额 | 近 12 月申请金额 vs 已通过金额双折线 |
+| 22 | 费用报销明细 | 累计已核销总额/本月笔数/待审批笔数/待付款笔数 | 近 12 月报销金额 vs 审批通过金额双折线 |
+| 23 | 加班明细 | 本月累计净工时/本月次数/累计调休小时/加班费次数 | 近 12 月加班工时柱状图(按类型分色堆叠) |
+| 24 | 用车明细 | 本月次数/累计里程/路桥停车费/未还车数 | 近 12 月用车次数 vs 费用双轴折线 |
+| 25 | 外勤日志明细 | 本月拜访次数/拜访客户数(去重)/均分/未点评数 | 近 12 月拜访次数 vs 平均评分双轴折线 |
+
+**经理版增量**:柱状图点击柱体→下方列表联动过滤该员工本月单据→可穿透跳详情页
+
+**财务版增量(仅页面 22)**:多维级联筛选(部门树+项目组)+ `[数据流水一键导出]`→Loading→.NET 生成 Excel→MethodChannel 唤起系统分享面板
+
+---
+
+## 二、审批人/经理 (Approver) 角色视图
+
+审批人拥有与员工完全对称的 26 个页面路由体系,但通过 OA 权限判定自动发生 UI 变体。
+
+### 复合角色优先级
+
+`OaUserPermission` 判定工作台聚合优先级:**Admin > Finance > Approver > Employee**。高优先级角色专属区块逐层叠加。权限列表在 App 启动时一次性拉取并写入全局 Provider。
+
+### 工作台与列表页变体
+
+**页面 3(经理版)**:金刚区上方插入【待我处理的审批】高规格动态卡片,红色角标待办数(`GET /api/oa/approval/pending-count`),点击→`/messages`(筛选 approval_notice)
+
+**页面 6/7/11/14/17(经理版)**:
+- 导航栏下方 `[我的发起]` / `[下属审批]` 范围标签
+- `[下属审批]`→骨架屏加载部门下属单据(`GET /api/oa/approval/subordinates`),卡片追加申请人姓名-部门
+- pending 下属卡片左滑→`[一键同意]`(绿),就地审批
+
+### 审批详情页三元控制操作栏
+
+**页面 8/9/12/15(经理版)**:
+- 键值对信息区上方:风控参考看板(申请人本月同类单据数+预算消耗进度条)
+- 底部操作栏:`[拒绝]`(白底红字,必填≥5字理由)+ `[转交]`(灰底,MethodChannel 通讯录单选)+ `[同意]`(`#00ABF3`,下流转)
+- 所有审批动作调 `POST /api/oa/approval/action`
+
+**页面 18(经理版)**:底部【主管批示及打分专区】(TDRate 5 星 + 文字输入 + 发送按钮)
+
+**页面 21~25(经理版)**:数据范围自动升级为部门聚合。图表渲染部门内部员工横向对比柱状图。点击柱体→联动过滤+穿透跳详情页。
+
+---
+
+## 三、财务人员 (Finance) 角色视图
+
+财务人员专注于发票查验、线下付款确认及凭证归档。
+
+**权限要求**:`oa.expense.mark_paid` + `oa.*.view_all` + `oa.report.export`
+
+**页面 3(财务版)**:全公司级财务看盘(已支付流水/待付款总额/异常退回数)
+
+**页面 7(财务版)**:
+- 全公司检索权 + 独家 `[待付款]` Chip
+- 卡片左滑→`[线下已付款]`快捷标记
+- 其他列表(事前/加班/用车)为全公司只读访问,无专属操作
+
+**页面 9(财务版-核心核销专区)**:
+- 明细下方:发票合规查验区(三 TDCheckbox:发票已验真/税号一致/类目合规)
+- 全部勾选→解锁打款按钮;漏勾→持续锁定
+- `[退回修改]`→选退回节点(员工重填/经理重审)→状态倒流
+- `[确认已线下打款并归档]`→滑出凭证表单(电汇流水号+记账凭证号必填)→归档。按钮替换为 `[下一笔待付款]`→连续核销
+
+**页面 22(财务版)**:多维级联筛选 + `[数据流水一键导出]`→Excel→系统分享面板
+
+---
+
+## 四、系统管理员 (Admin) 角色视图
+
+管理员拥有最高权限,独有 2 个管理专属页。
+
+**权限要求**:`oa.admin.*`
+
+### 管理员专属操作栏
+
+**页面 8/9/12/15(管理员版)**:底部 `[强制终止]`(红底白字)+ `[查看审批链]`(含所有历史 instanceId 流程)。不展示经理版同意/拒绝/转交。
+
+### 公告审计与发布
+
+**页面 20(管理员版)**:公告底部【触达率审计追踪卡片】(已读/未读人数+员工列表+TDDialog 头像详情)。`[一键 DING]`→强推 Push/短信。
+
+**页面 26:公告发布 (`/announcement/create`)**:
+- 标题 TDInput + 分类下拉(notice/policy/activity)
+- 富文本编辑域(加粗/斜体/下划线/列表/图片/链接/字号 H1-H3)
+- 附件上传 ≤5 个(PDF/图片/Word/Excel,≤20MB)
+- 置顶 TDSwitch + 有效期 TDDatePicker(可空=永不过期)
+- 接收范围选择器(右侧滑出部门树多选 Checkbox,默认全员;底部统计覆盖人数)
+- 右上角 `[预览]`→TDDialog 全屏模拟详情页
+- 底部:`[存为草稿]`(仅创建者+管理员可见)+ `[预览]` + `[发布]`
+- `[发布]`→确认弹窗"确认向 N 名员工发布?发布后不可撤回。"→写入 Announcement + 异步初始化 AnnouncementReadLog → Toast
+
+### 权限管理
+
+**页面 27:权限管理 (`/admin/permissions`)**:
+- TDSearchBar 防抖搜索(300ms)→ `GET /api/user/search?q=`
+- 员工列表(每页 20 条,默认按部门+姓名排序)
+- 点击员工卡片→右侧 TDDrawer 权限编辑抽屉:
+  - 快捷套餐按钮 ×4(员工/审批人/财务/管理员)
+  - 权限点复选框矩阵(~20 个权限点,按模块分组)
+  - 启用/禁用 TDSwitch
+- `[确认保存]`→ `PUT /api/oa/user-permissions` → 写 `OaPermissionChangeLog`
+- 【变更记录】折叠区→TDTimeline 展示最近 20 条(`OaPermissionChangeLog`)
+- 自保护:无法取消自己的 admin 权限 → Toast
+- 最后管理员保护:移除最后 admin→后端拒绝
+- 抽屉关闭(有未保存修改)→确认弹窗
+
+---
+
+## 五、全局设计规范
+
+### 5.1 色彩体系
+
+| Token | 值 | 用途 |
+|-------|------|------|
+| `--primary` | `#00ABF3` | 主按钮/激活态/高亮 Chip/链接 |
+| `--primary-light` | `#E6F7FD` | 主色浅底 |
+| `--primary-active` | `#0089C4` | 主色按压态 |
+| `--success` | `#00A870` | 已通过/同意/金额正面 |
+| `--warning` | `#E37318` | 审批中/超支警告/排期冲突 |
+| `--danger` | `#D54941` | 已拒绝/拒绝/删除/强制终止 |
+| `--text-primary` | `#1A1A1A` | 标题/关键金额 |
+| `--text-secondary` | `#666666` | 摘要/辅助说明/时间戳 |
+| `--text-placeholder` | `#BFBFBF` | 占位符/禁用文字 |
+| `--bg-page` | `#F5F5F5` | 页面背景 |
+| `--bg-card` | `#FFFFFF` | 卡片/表单底色 |
+| `--border` | `#E7E7E7` | 分割线/描边/输入框边框 |
+| `--status-gray` | `#999999` | 草稿/已撤回/已过期 |
+
+### 5.2 字号层级
+
+| Token | 字号 | 行高 | 用途 |
+|-------|------|------|------|
+| `--fs-title` | 18sp | 26px | 导航栏标题/详情大标题 |
+| `--fs-subtitle` | 16sp | 24px | 卡片标题/表单分组/金额大字/按钮 |
+| `--fs-body` | 14sp | 22px | 正文/列表摘要/输入框/Chip |
+| `--fs-caption` | 12sp | 18px | 辅助说明/时间戳/状态标签/红点数 |
+
+### 5.3 间距系统
+
+| Token | 值 | 用途 |
+|-------|------|------|
+| `--space-xs` | 4px | 图标与文字间距/Chip 内边距 |
+| `--space-sm` | 8px | 列表项内 padding/表单行间距 |
+| `--space-md` | 16px | 卡片内边距/页面左右边距 |
+| `--space-lg` | 24px | 表单分组间距/卡片间距 |
+| `--space-xl` | 32px | 页面顶部/底部留白 |
+
+### 5.4 空状态
+
+| 场景 | 文案 |
+|------|------|
+| 消息列表为空 | "暂无消息通知" |
+| 申请列表为空 | "暂无记录,点击下方按钮发起申请" |
+| 审批列表为空 | "暂无待审批单据" |
+| 报销明细为空 | "暂无报销明细,请先添加费用明细" |
+| 照片墙为空 | "暂无附件" |
+| 搜索无结果 | "未找到匹配的员工,试试其他关键词" |
+| 公告列表为空 | "暂无行政公告" |
+| 报表无数据 | "所选时间范围内暂无数据" |
+
+### 5.5 加载态
+
+| 场景 | 方式 |
+|------|------|
+| 页面首次加载 | TDSkeleton 骨架屏(最长 8s 超时转网络异常态) |
+| 下拉刷新 | RefreshIndicator |
+| 上拉加载更多 | 底部菊花 + "加载中..." |
+| 按钮提交中 | 按钮替换为 TDLoading 小菊花 + 禁用态 |
+| 附件上传中 | 缩略图叠加半透明蒙层 + 中心菊花 |
+| 一键导出中 | 圆形悬浮按钮旋转 + "导出中..." |
+
+### 5.6 网络异常态
+
+| 场景 | 处理 |
+|------|------|
+| 列表加载失败 | TDEmpty + 断网图标 + `[点击重试]` |
+| 提交失败 | TDToast(红)"提交失败,请稍后重试" |
+| 接口超时(>15s) | TDEmpty + 时钟图标 + `[点击重试]`"请求超时" |
+| 服务端 500 | TDToast"服务器繁忙,请稍后重试" |
+| 401 未授权 | 静默触发宿主登录流程 |
+
+### 5.7 并发冲突
+
+| 场景 | 处理 |
+|------|------|
+| 编辑草稿时单据已被他人提交 | `CONCURRENCY_CONFLICT`→TDDialog"已被修改或删除"→返回列表刷新 |
+| 审批时单据已被他人处理 | `APPROVAL_CONFLICT`→详情页自动刷新+Toast"状态已更新" |
+
+### 5.8 全局交互
+
+- **横竖屏**:仅支持竖屏
+- **键盘**:聚焦时页面上移;金额类弹数字键盘;文本类弹默认键盘
+- **返回手势**:iOS 边缘右滑;Android 系统返回键;表单有未保存修改时拦截确认
+- **Deep Link**:`tboss://oa/{path}` → GoRouter 路由
+- **字体缩放**:sp 单位,最小 11sp,导航栏标题不溢出
+
+### 5.9 导航栏标题清单
+
+| 页面 | 路由 | 标题 |
+|------|------|------|
+| 1 | `/` | —(Appshell) |
+| 2 | `/messages` | "消息通知" |
+| 3 | `/home` | "TBOSS 工作台" |
+| 3.1 | `/profile` | "我的" |
+| 4 | `/expense-apply/apply` | "事前申请" |
+| 5 | `/expense/apply` | "费用报销" |
+| 6 | `/expense-apply/list` | "申请记录" |
+| 7 | `/expense/list` | "报销记录" |
+| 8 | `/expense-apply/detail/:id` | "申请详情" |
+| 9 | `/expense/detail/:id` | "报销详情" |
+| 10 | `/overtime/apply` | "加班申请" |
+| 11 | `/overtime/list` | "加班记录" |
+| 12 | `/overtime/detail/:id` | "加班详情" |
+| 13 | `/vehicle/apply` | "用车申请" |
+| 14 | `/vehicle/list` | "用车记录" |
+| 15 | `/vehicle/detail/:id` | "用车详情" |
+| 16 | `/outing-log/create` | "外勤日志" |
+| 17 | `/outing-log/list` | "外勤记录" |
+| 18 | `/outing-log/detail/:id` | "日志详情" |
+| 19 | `/announcement/list` | "公司公告" |
+| 20 | `/announcement/detail/:id` | "公告详情" |
+| 21 | `/report/expense-apply-detail` | "事前申请明细报表" |
+| 22 | `/report/expense-detail` | "费用报销明细报表" |
+| 23 | `/report/overtime-detail` | "加班明细报表" |
+| 24 | `/report/vehicle-detail` | "用车明细报表" |
+| 25 | `/report/outing-log-detail` | "外勤日志明细报表" |
+| 26 | `/announcement/create` | "发布公告" |
+| 27 | `/admin/permissions` | "权限管理" |
+
+---
+
+> **文档版本**:v1.0 | 日期:2026-06-03

+ 605 - 0
docs/superpowers/specs/tboss-oa-interactions.md

@@ -0,0 +1,605 @@
+# TBOSS OA 模块 — 交互设计
+
+> 版本:v1.0 | 日期:2026-06-03 | 基于 `tboss-oa-product-strategy.md`
+>
+> 覆盖 27 个页面的所有可交互元素及其操作逻辑。数据源标注遵循 v2.0 架构(审批走 ERP、用户/组织/客户走 ERP、消息走 .NET 服务端、权限走 OA ACL)。
+
+---
+
+## 1. 全局数据源
+
+| 数据 | 来源 | 说明 |
+|------|------|------|
+| 用户信息(姓名/部门/岗位/头像) | .NET `GET /api/user/{id}` | 非本地表 |
+| 组织架构树 | .NET `GET /api/dept/tree` | 非本地表 |
+| 客户数据 | .NET `GET /api/customer/search?q=` | 非本地表 |
+| 审批流程(状态/时间线/待办) | .NET `GET/POST /api/oa/approval/*` | 服务端转发 ERP |
+| 消息通知 | .NET 消息模块 API | 服务端已有模块 |
+| OA 权限 | 本地 `OaUserPermission` JOIN `OaPermission` | OA 自管 |
+| 业务单据 | 本地 OA 业务表 | OA 自管 |
+| 车辆池 | 本地 `SysVehicle` | OA 自管 |
+| 费用类别 | 本地 `SysCostCategory` | OA 自管 |
+| 轮播图 | 本地 `SysBanner` | OA 自管 |
+| 银行列表 | .NET `GET /api/dict/banks` | 服务端字典 |
+
+---
+
+## 2. 页面跳转关系总图
+
+```
+/ (Appshell)
+├─ /messages ──────────────────────────────────────────────────────
+│   └─ (点击) → /announcement/detail/:id
+│            → /expense-apply/detail/:id  /expense/detail/:id
+│            → /overtime/detail/:id       /vehicle/detail/:id
+│
+├─ /home ──────────────────────────────────────────────────────────
+│   ├─ (轮播图) → 公告/活动链接 或 全屏预览
+│   ├─ (金刚区-发起) → /expense-apply/apply  /expense/apply
+│   │                → /vehicle/apply         /overtime/apply
+│   ├─ (金刚区-记录) → /expense-apply/list    /expense/list
+│   │                → /outing-log/list       /announcement/list
+│   ├─ (看板) → /expense-apply/list  /report/expense-detail  /messages
+│
+└─ /profile
+    ├─ 审批历史 → (子页面) → 各详情页
+    ├─ 我的报表 → (子页面) → /report/.. (5 Tab)
+    └─ 关于 → (子页面)
+
+表单页 ──(存草稿/提交)──→ 列表页 ──(点击卡片)──→ 详情页
+    ↑                         │                    │
+    └──(左滑编辑/draft编辑)───┘  (rejected→重新编辑)┘
+
+/admin/permissions  (管理员)
+/announcement/create (管理员)
+```
+
+---
+
+## 3. 页面详细交互
+
+### 页面 4.0:借款/备用金表单 (`/loan/apply`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 借款类型 TDPicker | 选择 | 备用金/差旅借款 | Loan.LoanType |
+| 借款金额 TDInput | 输入 | 纯数字 | Loan.Amount |
+| 借款事由 TDTextarea | 输入 | | Loan.Purpose |
+| [存为草稿]/[提交审批] | 点击 | 复用标准逻辑,提交调 `POST /api/oa/loan/submit` | — |
+
+### 页面 4.0a:借款列表 (`/loan/list`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| TDChip(全部/草稿/审批中/已通过/已拒绝/已撤回) | 点击 | 标准筛选 | ApprovalStatus 缓存 |
+| 卡片额外展示 | — | 还款状态标签(未还/部分已还/已还清/已核销) | Loan.RepaymentStatus |
+| 已通过+未还清卡片左滑 | 手势 | `[登记还款]` → 选还款方式+金额 → `POST /api/oa/loan/repay` | — |
+
+### 页面 4.0b:借款详情 (`/loan/detail/:id`)
+
+已通过且未还清的借款底部展示还款记录列表(LoanRepayment 按时间倒序)。
+
+---
+
+
+### 页面 1:Appshell 底部导航 (`/`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 消息 Tab | 点击 | 路由 → `/messages`,图标变 `#00ABF3` | — |
+| 消息 Tab 角标 | 数据绑定 | 未读消息数;进入时拉取,30s 轮询或 Push 刷新 | .NET 消息模块 API |
+| 工作台 Tab | 点击 | 路由 → `/home` | — |
+| 我的 Tab | 点击 | 路由 → `/profile` | — |
+| 当前激活 Tab | 重复点击 | 若含列表 → ScrollToTop | — |
+
+---
+
+### 页面 2:消息通知聚合页 (`/messages`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 导航栏标题 | — | "消息通知" | — |
+| "全部已读" | 点击 | 仅未读>0 时显示;调用 API,红点淡出,按钮消失 | .NET 消息模块 API |
+| 消息卡片(公告类) | 点击 | → `/announcement/detail/:id` | `MsgType='announcement'` |
+| 消息卡片(审批待办) | 点击 | 按 BizType 路由 → 对应详情页,底部展审批操作栏 | `BizType` 决定路由 |
+| 消息卡片(审批结果) | 点击 | 按 BizType 路由 → 对应详情页 | `BizType` 决定路由 |
+| 已读消息卡片 | — | 透明度 0.6,无红点,不可左滑 | `IsRead=1` |
+| 未读卡片-左滑 | 手势 | 滑出 `[标记已读]`(蓝) + `[删除]`(红) | — |
+| `[标记已读]` | 点击 | 成功→红点淡出+透明度↓;失败→Toast"操作失败,请重试" | .NET 消息模块 API |
+| `[删除]` | 点击 | TDDialog 确认 → 卡片收起 | .NET 消息模块 API |
+| 列表 | 下拉/上拉 | 刷新 page=1 / page++ 追加 | .NET 消息模块 API |
+| 空列表 | — | TDEmpty + 铃铛:"暂无消息通知" | — |
+
+---
+
+### 页面 3:工作台 (`/home`)
+
+**角色判定**(决定工作台变体):查 `OaUserPermission`:
+- 有 `oa.admin.*` → 管理员版
+- 无 admin 但有 `oa.expense.mark_paid` → 财务版
+- 无上述但有 `oa.*.approve` 或 `oa.*.view_dept` → 经理版
+- 其余 → 员工版
+
+#### 员工版
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 轮播图 | 自动/点击 | 3s 切换;有 LinkUrl→跳转,无→全屏预览(双指缩放) | `SysBanner` (IsActive=1, IsDeleted=0) |
+| 金刚区-事前申请 | 点击 | → `/expense-apply/apply` | — |
+| 金刚区-费用报销 | 点击 | → `/expense/apply` | — |
+| 金刚区-用车申请 | 点击 | → `/vehicle/apply` | — |
+| 金刚区-加班申请 | 点击 | → `/overtime/apply` | — |
+| 金刚区-申请记录 | 点击 | → `/expense-apply/list`(默认"全部") | — |
+| 金刚区-报销记录 | 点击 | → `/expense/list` | — |
+| 金刚区-外勤日志 | 点击 | → `/outing-log/list` | — |
+| 金刚区-公司公告 | 点击 | → `/announcement/list` | — |
+| 看板-已提单据 | 点击 | → `/expense-apply/list` | .NET 预计算(缓存 5min) |
+| 看板-本月报销 | 点击 | → `/report/expense-detail` | .NET 预计算 |
+| 看板-待处理 | 点击 | → `/messages`(筛选 approval_notice) | .NET 预计算 |
+| 页面整体 | 下拉 | 强制穿透缓存刷新 | — |
+
+#### 经理版增量
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 待审批卡片 TDBadge | — | 红色数字展示待办件数 | `GET /api/oa/approval/pending-count` |
+| 待审批卡片 | 点击 | → `/messages`(筛选 approval_notice) | — |
+
+#### 财务版增量
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 财务看盘卡片 | — | 已支付流水/待付款总额/异常退回数 | .NET 全公司聚合查询 |
+
+---
+
+### 页面 3.1:个人中心 (`/profile`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 头像 TDAvatar | 点击 | 唤起相册/相机→裁剪(1:1,≤2MB)→上传→全局刷新 | `PUT /api/user/avatar` → .NET → ERP |
+| 个人信息卡片 | — | 姓名/部门/岗位 | `GET /api/user/{erpUserId}` |
+| 我的审批历史 | 点击 | → 子页面 | — |
+| 我的报表 | 点击 | → 子页面(5 Tab) | — |
+| 关于 | 点击 | → 版本号/用户协议/隐私政策 | — |
+
+#### 子页面:我的审批历史
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 单据卡片-编号 | 点击 | 按 BizType 跳转详情页 | `GET /api/oa/approval/my-history` |
+| 列表 | 下拉/上拉 | 刷新/加载更多 | 同上 |
+| 空列表 | — | TDEmpty:"暂无审批记录" | — |
+
+---
+
+### 页面 4:事前申请表单 (`/expense-apply/apply`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 申请人姓名 | — | 只读 | `GET /api/user/{erpUserId}` → RealName |
+| 所属部门 | — | 只读 | `GET /api/user/{erpUserId}` → DeptName |
+| 申请日期 | — | 只读,DateTime.now(),YYYY-MM-DD | — |
+| 紧急程度 TDRadioGroup | 选择 | 普通/紧急/特急,默认"普通" | `ExpenseApplication.Urgency` |
+| 费用类型 TDCheckboxGroup | 多选 | 至少一项,联动过滤科目候选 | `ExpenseApplication.ExpenseTypes` |
+| 费用事由 TDTextarea | 输入 | ≤200 字 | `ExpenseApplication.Purpose` |
+| 关联项目 TDPicker | 级联 | 一级→ProjectService;二级→SubjectService | `ExpenseApplication.ProjectId` |
+| 预算科目 TDPicker | 选择 | 选定后调 BudgetService 加载可用余额 | `ExpenseApplication.BudgetSubjectId` |
+| 预算余额 | — | 只读 ¥XXXX.xx,ERP 无预算→隐藏区块 | .NET → ERP BudgetService |
+| 预估金额 TDInput | 输入 | 纯数字键盘;汇总超预算→变红+警告 | `ExpenseAppDetail.EstimatedAmount` |
+| [+ 添加费用明细] | 点击 | 展开新卡片,页面滚动聚焦 | — |
+| 明细卡片 ✕ | 点击 | 气泡确认→折叠消失 | — |
+| 附件上传 [+] | 点击 | 相册/相机/文件;≤10MB(图)/≤20MB(PDF) | `Attachment(BizType='expense_apply')` |
+| 附件缩略图 | 点击 | 黑底全屏预览,双指缩放(0.5x~3x),左右滑动 | — |
+| 附件 ⊖ | 点击 | 气泡确认→删除服务器文件 | — |
+| PDF 附件 | 点击 | 原生 PDF 查看器 | — |
+| [重置] | 点击 | 仅编辑草稿时可见;确认弹窗→清空 | — |
+| [存为草稿] | 点击 | Loading → PUT → Toast 绿→跳列表(激活"草稿"Chip) | `ExpenseApplication` (Status='draft') |
+| [提交审批] | 点击 | 校验→ScrollTo+红闪+TDMessage → Loading → POST → .NET → ERP 创建实例 → 存 ApprovalInstanceId → Toast → 跳列表 | .NET `POST /api/oa/expense-apply/submit` |
+| 返回(有未保存修改) | 点击 | TDDialog:"是否退出?" | — |
+
+---
+
+### 页面 5:费用报销表单 (`/expense/apply`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| [导入事前申请] | 点击 | 半屏多选抽屉:approved 且尚有额度的申请,每条填导入金额;可追加多条;空→TDEmpty | .NET API → `ExpenseApplication` (Status='approved', UsageStatus IN unused/partially_used) |
+| 对公/对私 TDSwitch | 切换 | personal→收款人账户;corporate→供应商名称+对公账户 | `Expense.PaymentType` |
+| 报销事由 | 输入 | 可导入回填或手工 | `Expense.Purpose` |
+| 成本中心 TDPicker | 选择 | 可空 | .NET → ERP CostCenterService |
+| 报销总金额 | — | 只读,明细累加上浮动画 | `Expense.TotalAmount` |
+| 开户行/户名/账号 | 输入 | 对私展示;前端校验账号 16-19 位 | `Expense.BankName/AccountName/BankAccount` |
+| 供应商名称/账户 | 输入 | 对公展示 | `Expense.SupplierName` |
+| 币种 TDPicker | 选择 | 默认 CNY,选外币→自动从 ERP 填汇率 | `ExpenseDetail.CurrencyCode/ExchangeRate` |
+| 费用类别 TDPicker | 选择 | 叶子节点 | `SysCostCategory` |
+| 原币金额/本币金额 | 输入/只读 | 原币×汇率=本币(服务端算) | `ExpenseDetail.Amount/BaseAmount` |
+| 分摊设置 | 输入 | 比例+目标部门/项目;不分摊留空 | `ExpenseDetail.AllocationPercent/AllocationDeptId/AllocationProjectId` |
+| 借款冲销 | 提交时 | .NET 查未还借款→弹窗确认冲销明细 | `GET /api/oa/loan/outstanding` |
+| [+ 添加费用明细] | 点击 | 平滑展开新卡片 | — |
+| 明细行 ✕ | 点击 | 气泡确认→折叠 | — |
+| 发票上传 [+] | 点击 | 相册/相机,≤10MB,满 9 隐藏;可绑定明细行 | `Attachment(BizType='expense')` |
+| 发票缩略图 | 点击 | 黑底全屏预览 | — |
+| [存为草稿]/[提交审批] | 点击 | 复用页面 4 逻辑 | — |
+| 返回(有修改) | 点击 | 复用页面 4 逻辑 | — |
+
+---
+
+### 页面 6/7:事前申请/费用报销列表页
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 导航栏 | — | "申请记录" / "报销记录" | — |
+| TDChip(全部/草稿/审批中/已通过/已拒绝/已撤回) | 点击 | 高亮 `#00ABF3`,列表淡入淡出刷新;无数据→TDEmpty(文案随 Chip) | ApprovalStatus 缓存 |
+| 列表 | 下拉/上拉 | 刷新/追加 | 本地 OA 表 + 后台静默刷新 ApprovalStatus |
+| 卡片空白区 | 点击 | → 详情页 | — |
+| 卡片左滑-[编辑] | 点击 | 仅 draft/rejected → 表单编辑态 | — |
+| 卡片左滑-[删除] | 点击 | 仅 draft/rejected → TDDialog 确认 → 软删除 | — |
+| StatusTag | — | 仅展示(灰/橙/绿/红/灰) | — |
+
+**经理版增量**:
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 范围标签 `[我的发起]` | 点击 | 员工版逻辑 | — |
+| 范围标签 `[下属审批]` | 点击 | 骨架屏→加载部门下属单据 | `GET /api/oa/approval/subordinates` |
+| 下属卡片左滑-[一键同意] | 点击 | 仅 pending;绿色按钮,就地审批 | `POST /api/oa/approval/action` |
+
+**财务版增量**(仅页面 7):
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| `[待付款]` Chip | 点击 | 筛选 Status='approved' AND PaymentStatus='unpaid' | 本地 Expense 表 |
+| 卡片左滑-[线下已付款] | 点击 | 标记 PaymentStatus='paid' | 本地 Expense 表 |
+
+---
+
+### 页面 8/9:事前申请/费用报销详情页
+
+#### 员工版
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 状态横幅 | — | 图标 + 状态中文 + 当前审批人姓名 | ApprovalStatus + 审批人姓名从 .NET API 获取 |
+| 提交时间 | — | YYYY-MM-DD HH:mm | CreateTime |
+| 费用明细"展开全部" | 点击 | >5 行时显示;平滑展开 | 本地子表 |
+| 附件缩略图 | 点击 | 黑底全屏预览 | 本地附件表 |
+| 审批时间线 | — | 纵向时间线渲染 | `GET /api/oa/approval/timeline?bizType=&bizId=` → .NET → ERP |
+| 时间线节点 | 点击 | 当前用户待审批→弹出审批操作;否则→展开详情 | — |
+| 审批人姓名/头像 | 点击 | TDDialog 展示基本信息卡片 | `GET /api/user/{approverId}` |
+| [编辑](draft) | 点击 | → 表单编辑态 | — |
+| [提交审批](draft) | 点击 | 校验→提交;失败→TDDialog 引导 | `POST /api/oa/approval/action?action=submit` |
+| [撤回申请](pending) | 点击 | TDDialog 确认 → Status='withdrawn' → 旧 instanceId 追加到 PreviousInstanceIds | `POST /api/oa/approval/withdraw` |
+| [重新编辑并发起](rejected) | 点击 | → 表单编辑态;提交后更新原记录+旧审批链失效 | — |
+
+#### 经理版底部操作栏(pending 状态 + 当前审批人=该经理)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| [拒绝] | 点击 | TDDialog 输入理由(≥5 字解锁)→ POST | `POST /api/oa/approval/action` {action: reject} |
+| [转交] | 点击 | MethodChannel 唤起通讯录单选 → POST | `POST /api/oa/approval/action` {action: transfer} |
+| [同意] | 点击 | Loading → POST → 节点变绿 → 流转 | `POST /api/oa/approval/action` {action: approve} |
+| 风控参考看板 | — | 申请人本月同类单据数 + 预算消耗进度条 | 本地查询 + .NET 转发 ERP |
+
+#### 管理员版底部操作栏
+
+| 元素 | 事件 | 行为 |
+|------|------|------|
+| [强制终止] | 点击 | TDDialog 确认 → Status='withdrawn' + 写入终止原因 |
+| [查看审批链] | 点击 | 展开完整审批历史(含历史 instanceId 的流程) |
+
+#### 财务版增量(仅页面 9)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 合规复选框 ×3 | 勾选 | 全部勾选→解锁 [确认打款] 按钮 | `Expense.IsInvoiceVerified/IsTaxIdMatched/IsCategoryCompliant` |
+| [退回修改] | 点击 | 选退回节点(员工/经理)→状态倒流 | .NET API |
+| [确认已线下打款并归档] | 点击 | 解锁后;滑出凭证表单(电汇流水号+记账凭证号必填)→提交→Paid | `Expense.BankTransferNo` + `VoucherNo` |
+| [下一笔待付款] | 点击 | 归档后替换显示;跳转下一条 approved+unpaid;无→"已无待付款" | — |
+
+---
+
+### 页面 10:加班申请表单 (`/overtime/apply`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 加班类型 TDPicker | 选择 | 工作日/休息日/节假日,默认"工作日";节假日标注费率 | `Overtime.OtType` |
+| 补偿方式 TDRadio | 选择 | 转调休/结算加班费/混合,默认"转调休" | `Overtime.CompensationType` |
+| 混合模式 TDSlider | 拖动 | 10%~90%,步长 10%;右侧实时比例文案 | `Overtime.CompLeaveRatio` |
+| 开始/结束时间 | 点击 | TDDatePicker 年月日时分;确认后自动算净工时(扣除 12:00-13:00/18:00-18:30) | `Overtime.StartTime/EndTime` → `NetOtHours` |
+| 净工时卡片 | — | 只读大字;≤0→变红+提交按钮置灰 | `Overtime.NetOtHours` |
+| 开始>结束校验 | — | 边框变红+提示+提交按钮置灰 | — |
+| 加班原因 TDTextarea | 输入 | `Overtime.Reason` | — |
+| [存为草稿]/[提交审批] | 点击 | 复用页面 4 逻辑;提交时 .NET 创建 ERP 审批实例 | — |
+| 返回(有修改) | 点击 | 复用页面 4 逻辑 | — |
+
+---
+
+### 页面 11:加班列表 / 页面 12:加班详情
+
+交互模式同页面 6/7 和 8/9。加班类型标签(工作日/休息日/节假日)、补偿方式展示(加班费/转调休/"X%调休+Y%结算")。员工端仅 `[撤回]`。
+
+---
+
+### 页面 13:用车申请表单 (`/vehicle/apply`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 车牌号 TDPicker | 选择 | 全公司车池;冲突车辆标红"冲突";无可用→TDEmpty | `SysVehicle` (IsDeleted=0) |
+| 车牌+出车/还车时间 | 失焦 | 异步排期冲突检测;冲突→红色文本+锁死提交按钮 | `Vehicle` (Status IN pending,approved) |
+| 用车事由 | 输入 | | `Vehicle.Reason` |
+| 始发地 | 定位/输入 | 原生定位匹配 | `Vehicle.Origin` + 经纬度 |
+| 目的地+地图图标 | 输入/点击 | MethodChannel 唤醒地图选点→回填地址+经纬度 | `Vehicle.Destination` + 经纬度 |
+| 出车/还车时间 | 点击 | TDDatePicker 年月日时分 | `Vehicle.StartTime/EndTime` |
+| 还车>出车校验 | — | 否则红色提示+按钮置灰 | — |
+| 同行总人数 | 输入 | 纯数字,0→1 | `Vehicle.PassengerCount` |
+| 随行同行人 [+] | 点击 | MethodChannel 唤醒通讯录多选→胶囊标签;取消→无变化 | `VehiclePassenger` |
+| 胶囊标签 x | 点击 | 剔除人员 | — |
+| [存为草稿]/[提交审批] | 点击 | 复用页面 4 逻辑 | — |
+
+---
+
+### 页面 14:用车列表 / 页面 15:用车详情
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| TDChip(全部/草稿/审批中/已通过/已拒绝/已撤回/已还车) | 点击 | 标准逻辑 | ApprovalStatus 缓存 |
+| 卡片左滑-[编辑]/[删除] | 点击 | 仅 draft/rejected | — |
+| 卡片左滑-[确认还车] | 点击 | 仅 approved;拉起半屏核销抽屉 | — |
+| 微缩地图 | 点击 | 唤醒原生地图导航 | 本地经纬度 |
+| [确认还车并登记] | 点击 | 滑出核销抽屉:实还时间/出车前里程/还车后里程/费用备注 | `Vehicle` 还车字段 |
+| 还车里程校验 | — | 还车后 ≥ 出车前,否则按钮置灰+红提示 | — |
+| 确认提交还车 | 点击 | TDDialog 确认→Status='returned'→页面刷新 | — |
+
+---
+
+### 页面 16:外勤日志创建 (`/outing-log/create`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| GPS 定位区 | 页面初始化 | 强制高精度 GPS → ReadOnly 展示地址;精度>100m→黄色警告 | MethodChannel → `OutingLog.CheckIn*` |
+| GPS 定位失败 | — | TDEmpty"无法获取当前位置";提交按钮置灰 | — |
+| 客户名称 TDInput | 输入 | 自动联想;无匹配→自由文本(提交时 .NET 自动创建 ERP 客户) | `GET /api/customer/search?q=` |
+| 工作总结 TDTextarea | 输入 | | `OutingLog.VisitSummary` |
+| 后续计划 | 输入 | | `OutingLog.NextPlan` |
+| 现场拍照 | 点击 | 跳过相册,直接相机;水印=服务器授时+GPS;最少1张最多4张 | Attachment(BizType='outing_log') |
+| 相机权限被拒 | — | TDDialog + "前往设置" | — |
+| [存为草稿]/[提交] | 点击 | 校验 GPS+照片≥1 | — |
+| 返回(有修改) | 点击 | 复用页面 4 逻辑 | — |
+
+---
+
+### 页面 17:外勤日志列表 / 页面 18:外勤日志详情
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| TDChip(全部/本月) | 点击 | 切换刷新 | — |
+| 卡片"新点评" TDBadge | — | 橙色标记 | `OutingLogComment.CreateTime > COALESCE(LastViewedTime, '1900-01-01')` 且评论者≠本人 |
+| 卡片左滑-[编辑]/[删除] | 点击 | 仅 draft | — |
+| 微缩地图 | 点击 | 唤醒原生地图导航 | 本地经纬度 |
+| 照片墙 | 点击 | 全屏预览 | Attachment(BizType='outing_log') |
+| 页面进入 | — | 无感 PUT 更新 LastViewedTime,列表"新点评"消失 | `PUT /api/oa/outing-log/:id/view` |
+| 主管点评区(员工端) | — | 只读浏览,不可回复 | `OutingLogComment` |
+
+**经理版增量**:
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| TDRate 星级 | 点选 | 1-5 星 | `OutingLogComment.RatingStars` |
+| 点评文本框 | 输入 | 批示文字 | `OutingLogComment.CommentText` |
+| 发送按钮 | 点击 | 气泡追加+动画 | `POST /api/oa/outing-log/:id/comment` |
+
+---
+
+### 页面 19:公告列表 (`/announcement/list`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| TDChip 类型筛选 | 点击 | 全部/通知公告/人事与制度/放假与活动/我的草稿(仅管理员) | `Announcement.Type` + `Status` |
+| 排序规则 | — | 置顶→未过期(PublishTime DESC)→已过期(PublishTime DESC) | `IsTop`, `PublishTime`, `ExpiryDate` |
+| 已过期卡片 | — | 整体置灰+标题末尾"已过期" | `ExpiryDate < NOW` |
+| 未读红点 | — | 返回列表后红点消失 | `AnnouncementReadLog.IsRead=0` |
+| 公告卡片 | 点击 | → `/announcement/detail/:id` | — |
+| 搜索 | — | 不支持 | — |
+
+---
+
+### 页面 20:公告详情 (`/announcement/detail/:id`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 正文 | — | HTML/Markdown 渲染 | `Announcement.Content` |
+| 已过期横幅 | — | 红色"该公告已于 YYYY-MM-DD HH:mm 过期" | `Announcement.ExpiryDate` |
+| 附件图标 | 点击 | 原生浏览器/下载管理器;失败→Toast | Attachment(BizType='announcement') |
+| 停留 ≥2s | — | 无感标记已读;<2s 返回不发送 | `POST /api/oa/announcement/:id/read` |
+
+**管理员版增量**:
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| [已读 N 人] Chip | 点击 | 展开已读员工列表(头像+部门) | `AnnouncementReadLog` (IsRead=1) + .NET 用户 API |
+| [未读 N 人] Chip | 点击 | 展开未读员工列表 | `AnnouncementReadLog` (IsRead=0) |
+| [一键 DING] | 点击 | 震动反馈→MethodChannel 强推 Push/短信 | `AnnouncementReadLog.IsUrged=1` |
+
+---
+
+### 页面 21~25:五大明细报表 (`/report/..`)
+
+| 元素 | 事件 | 行为 |
+|------|------|------|
+| TDDropdownMenu(本月/本季/本年) | 选择 | 数值卡片+图表 300ms 渐变刷新 |
+| 数值卡片 | — | 只读;空→0/¥0.00 |
+| fl_chart 数据点 | 长按 | tooltip(日期+数值) |
+| fl_chart 图表 | 水平滑动 | 查看历史数据 |
+| 图表为空 | — | TDEmpty:"所选时间范围内暂无数据" |
+
+**经理版增量**:柱状图柱体点击 → 下方列表联动过滤该员工本月单据 → 可穿透跳详情页。
+
+**财务版增量**(仅页面 22):部门树/项目组级联筛选 + `[数据流水一键导出]` → 后端生成 Excel → MethodChannel 原生分享。
+
+---
+
+### 页面 26:公告发布 (`/announcement/create`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| 公告标题 TDInput | 输入 | | `Announcement.Title` |
+| 分类下拉 | 选择 | notice/policy/activity | `Announcement.Type` |
+| 富文本编辑域 | 操作 | 加粗/斜体/下划线/列表/图片/链接/字号 | `Announcement.Content` |
+| 附件上传 [+] | 点击 | ≤5 个,PDF/图片/Word/Excel,≤20MB | Attachment(BizType='announcement') |
+| 置顶 TDSwitch | 切换 | 默认关闭 | `Announcement.IsTop` |
+| 有效期 TDDatePicker | 选择 | 非必填,不填永不过期 | `Announcement.ExpiryDate` |
+| 接收范围选择器 | 点击 | 右侧滑出部门树多选(Checkbox);默认全员;底部统计人数 | `AnnouncementTarget` |
+| [预览] | 点击 | TDDialog 全屏模拟详情页 | — |
+| [存为草稿] | 点击 | 保存(仅创建者+管理员可见) | `Announcement.Status='draft'` |
+| [发布] | 点击 | 确认弹窗 → 写入 + 异步初始化 AnnouncementReadLog → Toast | `Announcement.Status='published'` |
+| 返回(有修改) | 点击 | 复用页面 4 逻辑 | — |
+
+---
+
+### 页面 27:权限管理 (`/admin/permissions`)
+
+| 元素 | 事件 | 行为 | 数据来源 |
+|------|------|------|---------|
+| TDSearchBar | 输入 | 300ms 防抖模糊搜索→骨架屏→刷新;无结果→TDEmpty | `GET /api/user/search?q=` |
+| 员工列表 | 下拉/上拉 | 刷新/每页 20 条 | 默认按部门+姓名排序 |
+| 员工卡片 | 点击 | 右侧滑出 TDDrawer 权限编辑抽屉 | — |
+| 快捷套餐按钮 ×4 | 点击 | 一键赋予标准权限集(员工/审批人/财务/管理员) | `OaUserPermission` |
+| 权限点复选框 | 勾选 | 逐项加减 | `OaUserPermission` |
+| 启用/禁用 TDSwitch | 切换 | 经理有待审批时警告确认 | OA 权限状态 |
+| [确认保存] | 点击 | Loading → PUT → Toast + 写审计日志 | `OaUserPermission` + `OaPermissionChangeLog` |
+| 【变更记录】折叠区 | 展开 | TDTimeline 展示最近 20 条 | `OaPermissionChangeLog` |
+| 抽屉关闭(有未保存修改) | — | 确认弹窗"是否放弃?" | — |
+| 自保护 | — | 无法取消自己的 admin 权限 → Toast | — |
+| 最后管理员保护 | — | 后端拒绝移除最后一个 admin | — |
+
+---
+
+## 4. API 端点汇总
+
+### OA 业务 API(.NET 服务端新增)
+
+```
+# 权限
+GET    /api/oa/permissions
+GET    /api/oa/user-permissions?userId=
+PUT    /api/oa/user-permissions
+GET    /api/oa/permission-changelog?userId=&page=
+
+# 业务单据
+POST   /api/oa/expense-apply/submit
+POST   /api/oa/expense/submit
+POST   /api/oa/overtime/submit
+POST   /api/oa/vehicle/submit
+PUT    /api/oa/{bizType}/draft
+GET    /api/oa/{bizType}/list?status=&page=
+GET    /api/oa/{bizType}/detail/:id
+DELETE /api/oa/{bizType}/:id
+
+# 外勤日志
+PUT    /api/oa/outing-log/submit
+PUT    /api/oa/outing-log/:id/view
+POST   /api/oa/outing-log/:id/comment
+
+# 审批代理
+GET    /api/oa/approval/pending?userId=&bizType=&page=
+GET    /api/oa/approval/pending-count?userId=
+GET    /api/oa/approval/timeline?bizType=&bizId=
+POST   /api/oa/approval/action
+POST   /api/oa/approval/withdraw
+GET    /api/oa/approval/my-history?userId=&page=
+GET    /api/oa/approval/subordinates?approverId=&bizType=&page=
+
+# 公告
+POST   /api/oa/announcement/publish
+POST   /api/oa/announcement/:id/read
+POST   /api/oa/announcement/:id/ding
+GET    /api/oa/announcement/read-stats/:id
+
+# 报表
+GET    /api/oa/report/{type}?range=&userId=
+POST   /api/oa/report/expense/export
+
+# 车辆/轮播图
+GET    /api/oa/vehicles
+POST   /api/oa/vehicles
+PUT    /api/oa/vehicles/:id
+GET    /api/oa/banners
+```
+
+### 复用已有 API
+
+```
+GET    /api/user/{id}
+GET    /api/user/search?q=&page=
+GET    /api/dept/tree
+PUT    /api/user/avatar
+GET    /api/customer/search?q=
+GET    /api/messages?page=
+GET    /api/messages/unread-count
+PUT    /api/messages/:id/read
+POST   /api/messages/read-all
+DELETE /api/messages/:id
+GET    /api/dict/banks
+GET    /api/dict/cost-categories
+```
+
+---
+
+## 5. 全局交互规范
+
+### 5.1 空状态
+
+| 场景 | 文案 |
+|------|------|
+| 消息列表为空 | "暂无消息通知" |
+| 申请列表为空 | "暂无记录,点击下方按钮发起申请" |
+| 审批列表为空 | "暂无待审批单据" |
+| 搜索无结果 | "未找到匹配的员工,试试其他关键词" |
+| 公告列表为空 | "暂无行政公告" |
+
+### 5.2 加载态
+
+| 场景 | 方式 |
+|------|------|
+| 页面首次加载 | TDSkeleton 骨架屏(最长 8s 超时转网络异常态) |
+| 下拉刷新 | RefreshIndicator |
+| 上拉加载更多 | 底部菊花 + "加载中..." |
+| 按钮提交中 | 按钮替换为 TDLoading 小菊花 + 禁用态 |
+| 附件上传中 | 缩略图叠加半透明蒙层 + 中心菊花 |
+
+### 5.3 网络异常态
+
+| 场景 | 方式 |
+|------|------|
+| 列表加载失败 | TDEmpty + 断网图标 + [点击重试] |
+| 提交失败 | TDToast(红)"提交失败,请稍后重试" |
+| 接口超时(>15s) | TDEmpty + [点击重试] |
+| 401 未授权 | 静默触发宿主登录流程 |
+
+### 5.4 全局交互
+
+- **横竖屏**:仅支持竖屏
+- **键盘**:聚焦时页面上移;金额类弹数字键盘;文本类弹默认键盘
+- **返回手势**:iOS 边缘右滑;Android 系统返回键;表单页有未保存修改时拦截确认
+- **Deep Link**:`tboss://oa/{path}` → GoRouter 路由
+- **并发冲突**:编辑/审批冲突 → TDDialog 提示 → 刷新
+- **字体缩放**:sp 单位,最小 11sp,导航栏标题不溢出
+
+### 5.5 设计 Token
+
+| Token | 值 | 用途 |
+|-------|------|------|
+| `--primary` | `#00ABF3` | 主按钮/激活态/高亮/链接 |
+| `--success` | `#00A870` | 已通过/同意/金额正面 |
+| `--warning` | `#E37318` | 审批中/超支警告/冲突提示 |
+| `--danger` | `#D54941` | 已拒绝/拒绝/删除 |
+| `--text-primary` | `#1A1A1A` | 标题/关键金额 |
+| `--text-secondary` | `#666666` | 摘要/辅助说明 |
+| `--bg-page` | `#F5F5F5` | 页面背景 |
+| `--bg-card` | `#FFFFFF` | 卡片/表单底色 |
+| `--border` | `#E7E7E7` | 分割线/描边 |
+
+---
+
+> **文档版本**:v1.0 | 日期:2026-06-03

File diff suppressed because it is too large
+ 611 - 0
docs/superpowers/specs/tboss-oa-prd.md


+ 312 - 0
docs/superpowers/specs/tboss-oa-product-strategy.md

@@ -0,0 +1,312 @@
+# TBOSS OA 模块 — 产品思路
+
+> 版本:v1.0 | 日期:2026-06-03 | 状态:已确认
+
+---
+
+## 1. 模块定位
+
+在已有 ERP App 内嵌入一个 OA 功能模块(Flutter),后端在现有 .NET Framework 4.8 服务端上新增 API。
+
+**OA 只负责**:业务表单的录入与展示、公告管理、报表展示、OA 独立权限控制。
+
+**不复建的已有能力**:用户与组织架构、审批引擎、消息通知推送、客户主数据——均复用 ERP/.NET 服务端。
+
+**Why**:公司已有成熟 ERP 和多套审批 API,消息通知也在 .NET 服务端上跑着。OA 是一个移动端业务前端,不是独立系统。
+
+---
+
+## 2. 系统架构
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                 Flutter OA 模块                              │
+│  统一的 REST API 调用(只调 .NET 服务端)                       │
+└──────────────────────────┬──────────────────────────────────┘
+                           │
+                           ▼
+┌─────────────────────────────────────────────────────────────┐
+│             .NET Framework 4.8 服务端(已有 + 新增)            │
+│                                                              │
+│  已有(复用):                                               │
+│  ├─ 用户/部门/客户数据 ── 来自多套 ERP 主数据                    │
+│  ├─ 消息通知推送 ────── App Push + 站内消息                    │
+│  └─ 审批操作代理 ────── 适配器层(每套 ERP 一个 Adapter)       │
+│                                                              │
+│  新增 OA 能力:                                               │
+│  ├─ 权限管理 ── OA 独立权限点 + 快捷套餐                        │
+│  ├─ 业务单据 ── 报销/事前/加班/用车/外勤 CRUD + 审批操作入口      │
+│  ├─ 公告管理 ── 发布/浏览/已读追踪/DING 催办                    │
+│  └─ 报表数据 ── 5 大明细报表聚合查询                           │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**Why**:
+
+- **Flutter 只调 .NET 服务端**:多 ERP 适配、消息推送、状态刷新全部下沉到服务端,移动端不感知复杂度
+- **无 ERP 回调**:ERP 审批完成后不会主动通知 OA。状态同步策略为"列表页缓存秒开 + 后台静默刷新 + 详情页实时校准"
+- **消息通知复用**:审批通过/拒绝后的通知由 .NET 服务端已有消息模块直接从 ERP 侧触发推送,OA 不负责
+
+---
+
+## 3. OA 独立权限模型
+
+### 3.1 设计背景
+
+**约束**:
+- ERP 采用 ACL 模型:按单据/模块给每个用户赋予增删改查权限,没有预设的"财务""采购""销售"角色
+- OA 端也需要审批单据(事前申请、报销单、用车申请、加班申请)
+- ERP 的权限和 OA 的权限是两套独立体系
+
+**讨论过程**:是否保留 4 角色?→ "审批人"和"财务"在 OA 端如何定义?→ 结论:角色是权限的快捷套餐,底层是 ACL。
+
+### 3.2 权限点清单(~20 个)
+
+```
+报销:
+  oa.expense.apply            发起报销
+  oa.expense.view_own         查看自己的报销单
+  oa.expense.view_dept        查看本部门报销单
+  oa.expense.view_all         查看全公司报销单
+  oa.expense.approve          执行审批操作(显示审批按钮)
+  oa.expense.mark_paid        标记已付款(财务核销)
+
+事前申请:
+  oa.expense_apply.apply
+  oa.expense_apply.view_own / view_dept / view_all / approve
+
+加班:
+  oa.overtime.apply / view_own / view_dept / view_all / approve
+
+用车:
+  oa.vehicle.apply / view_own / view_dept / view_all / approve
+
+外勤日志:
+  oa.outing_log.create        写日志
+  oa.outing_log.view_own / view_dept / view_all
+  oa.outing_log.comment       点评打分
+
+公告:
+  oa.announcement.view        浏览公告
+  oa.announcement.publish     发布公告
+  oa.announcement.manage      管理(编辑/删除/置顶)
+
+报表:
+  oa.report.view_own / view_dept / view_all
+  oa.report.export            导出 Excel
+
+管理:
+  oa.admin.permissions        管理 OA 权限
+  oa.admin.vehicles           管理车辆池
+  oa.admin.customers          管理客户池
+  oa.admin.banners            管理工作台轮播图
+```
+
+### 3.3 快捷套餐
+
+| 套餐 | 包含 | 适用 |
+|------|------|------|
+| **员工** | apply + view_own + create + announcement.view | 默认 |
+| **审批人** | 员工 + approve + view_dept + outing_log.comment | 部门主管 |
+| **财务** | view_all + expense.mark_paid + report.view_all + report.export | 核销人员 |
+| **管理员** | 全部权限 | IT/HR 管理员 |
+
+### 3.4 "审批人"和"财务"的定义
+
+**审批人角色 ≠ 可以审批所有单据。** OA 端只是:
+- 解锁审批操作栏 UI(同意/拒绝/转交按钮)
+- 解锁部门级数据范围(列表页"下属审批"标签、部门报表)
+- 解锁外勤日志点评功能
+
+具体谁能审批哪个单据,**由 ERP 审批引擎的审批链配置决定**,OA 只是把操作入口和审批结果展示出来。
+
+**财务角色同理**:解锁发票合规核销区 + 打款归档按钮 + 全公司数据 + 导出。不参与正常审批流。
+
+**Why**:ERP 已经有完整的审批引擎和审批链配置,OA 侧的角色本质上是功能可见性开关 + 数据范围边界,不是数据权限的最终裁判。
+
+---
+
+## 4. 审批对接方案
+
+### 4.1 OA 不存审批配置和记录
+
+审批链配置、审批记录、审批流转全部由 ERP 审批引擎管理。
+
+**Why**:审批引擎是 ERP 核心能力,OA 是消费者而非替代者。OA 存一份审批数据会造成双写和一致性难题。
+
+### 4.2 业务表新增字段
+
+每张需要审批的业务主表(ExpenseApplication / Expense / Overtime / Vehicle):
+
+```
+ApprovalInstanceId     VARCHAR(50)     -- 当前有效的 ERP 审批实例 ID
+ApprovalStatus         VARCHAR(20)     -- OA 本地缓存状态(列表页秒开用)
+PreviousInstanceIds    VARCHAR(MAX)    -- 历史实例 ID JSON 数组
+                                       -- 驳回重新发起时追加旧 ID
+```
+
+**Why 3 个字段**:
+
+- `ApprovalInstanceId` 是必须的,关联 ERP 审批实例
+- `ApprovalStatus` 是性能缓存:列表页 20 条单据如果每条都跨系统查 ERP 会卡。缓存状态下拉刷新时校准
+- `PreviousInstanceIds` 用于追溯驳回重新提交前的审批历史。如果 ERP 能按 bizType+bizId 直接查全部审批历史则不需要此字段(待确认)
+
+### 4.3 状态同步策略
+
+**约束**:ERP 审批完成后没有回调 OA 的机制。
+
+| 场景 | 策略 |
+|------|------|
+| 列表页 | 展示本地缓存 ApprovalStatus(秒开);后台静默批量刷新 |
+| 详情页 | 打开时实时从 ERP 刷新校准 |
+| 下拉刷新 | 强制穿透缓存 |
+| 审批操作后 | 立即写缓存 + 异步校准 |
+| 消息通知 | 由 .NET 服务端消息模块在审批完成后从 ERP 侧触发推送 |
+
+**Why**:没有回调的情况下,列表页每次全量跨系统查询太慢,缓存 + 按需刷新是代价最低的平衡方案。
+
+### 4.4 多 ERP 适配
+
+公司有多套 ERP 系统,审批 API 接口签名不一致。适配工作全部在 .NET 服务端完成:
+
+```
+.NET 服务端
+  ┌──────────────────────────┐
+  │  OA ApprovalService      │  ← 统一接口(Flutter 调的)
+  ├──────────────────────────┤
+  │  ErpAAdapter             │  ← 翻译 ERP-A 的 API 格式
+  │  ErpBAdapter             │  ← 翻译 ERP-B 的 API 格式
+  │  ErpCAdapter             │  ← 翻译 ERP-C 的 API 格式
+  └──────────────────────────┘
+```
+
+每套 ERP 需要提供的能力:
+
+| OA 需要的 | ERP 审批 API 提供 |
+|----------|------------------|
+| 提交单据进入审批流 | 发起审批 |
+| 获取单据当前审批状态 | 查询实例状态 |
+| 获取审批时间线 | 查询流程节点历史 |
+| 获取某人的待审批列表 | 查询待办 |
+| 执行审批(同意/拒绝/转交) | 审批动作接口 |
+
+**Why**:Flutter 端只需要统一的 REST API,多 ERP 的差异由服务端适配器模式隔离,后续新增 ERP 只需增加一个 Adapter。
+
+---
+
+## 5. 功能模块与数据范围
+
+### 5.1 模块总览
+
+| 模块 | 走审批 | 核心数据来源 |
+|------|--------|------------|
+| 事前申请 | ✅ ERP | OA 业务表 + ERP 审批实例 |
+| 费用报销 | ✅ ERP | OA 业务表 + ERP 审批实例 |
+| 加班申请 | ✅ ERP | OA 业务表 + ERP 审批实例 |
+| 用车申请 | ✅ ERP | 车辆池 OA 自管;审批走 ERP |
+| 外勤日志 | ❌ | OA 自闭环;客户名关联 ERP 客户 ID |
+| 公告 | ❌ | OA 完全自闭环 |
+| 报表 | ❌ | OA 业务表 + ERP 审批状态 |
+
+### 5.2 数据所有权
+
+```
+OA 独立维护:
+  ├─ 所有业务单据及明细(报销/事前/加班/用车/外勤/公告)
+  ├─ 附件(支撑材料/发票影像/外勤照片/公告附件)
+  ├─ 外勤点评
+  ├─ 车辆池(ERP 不管公车)
+  ├─ 公告触达审计(已读/未读/DING)
+  ├─ OA 权限数据
+  └─ 轮播图配置
+
+从 ERP 查询(不持久化):
+  ├─ 用户信息(姓名/部门/岗位/头像)
+  ├─ 组织架构树
+  ├─ 客户数据
+  ├─ 审批流程数据(状态/时间线/待办)
+  └─ 项目/预算科目/成本中心
+```
+
+---
+
+## 6. 数据库表变更
+
+### 6.1 删除(不再由 OA 管理)
+
+| 表 | 原因 |
+|----|------|
+| SysUser | ERP 管理用户 |
+| SysRole / SysUserRole | 改为 OA 权限模型 |
+| SysRoleChangeLog | 改为 OaPermissionChangeLog |
+| SysDepartment | ERP 管理组织架构 |
+| SysCostCenter / SysProject / SysBudgetSubject | ERP 管理,通过 .NET 适配器查询 |
+| SysCustomer / SysCustomerContact | ERP 管理客户,通过 .NET 适配器查询 |
+| SysBank | .NET 服务端字典 |
+| ApprovalChain / ApprovalRecord | ERP 审批引擎管理 |
+| Message | .NET 服务端消息模块管理 |
+| SysProjectBudget / ExpenseStandard | ERP 管理,通过 BudgetService / StandardService 适配器 |
+
+### 6.2 新增
+
+```
+OaPermission         -- 权限点字典
+OaUserPermission     -- 用户-权限关联(基于 ERP UserId)
+OaPermissionChangeLog -- 权限变更审计
+ExpenseApplicationMapping -- 申请↔报销多对多
+ExpenseApplicationChange -- 申请变更/追加记录
+Loan / LoanRepayment  -- 借款/备用金及还款
+```
+
+### 6.3 业务表变更
+
+所有审批类主表(ExpenseApplication / Expense / Overtime / Vehicle)新增:
+- `ApprovalInstanceId VARCHAR(50)`
+- `ApprovalStatus VARCHAR(20)`
+- `PreviousInstanceIds VARCHAR(MAX)`
+
+引用 ERP 数据的字段改为存储 ERP ID(非 FK 约束):
+- `DeptId`、`ProjectId`、`BudgetSubjectId`、`CostCenterId` → ERP 对应实体 ID
+- `OutingLog.CustomerId` → ERP 客户 ID
+
+### 6.4 保留不变
+
+OA 自管的核心表保持原有结构:
+- 业务主表:ExpenseApplication, Expense, Overtime, Vehicle, OutingLog, Announcement
+- 业务子表:ExpenseAppDetail, ExpenseDetail, VehiclePassenger
+- 附件表:Attachment(统一聚合,BizType 区分)
+- 借款表:Loan, LoanRepayment
+- 申请↔报销关联:ExpenseApplicationMapping(多对多)
+- 申请变更:ExpenseApplicationChange
+- 互动/审计表:OutingLogComment, AnnouncementReadLog, AnnouncementTarget
+- 基础表:SysVehicle, SysBanner
+
+---
+
+## 7. 竞品对标
+
+| 维度 | 钉钉/企微 | 蓝凌/泛微 | **TBOSS OA** |
+|------|----------|----------|-------------|
+| 部署方式 | SaaS | 私有化/SaaS | **嵌入 ERP App,非独立产品** |
+| 审批引擎 | 自带 | 自带 | **复用 ERP,适配器隔离多套差异** |
+| 用户体系 | 独立维护 | 独立/AD 同步 | **映射 ERP 用户,零重复维护** |
+| 预算控制 | 提醒级 | 冻结级 | 取决于 ERP 预算能力 |
+| 角色模型 | 链式主管 | 岗位/相对角色 | **权限点 ACL + 快捷套餐** |
+| 移动端深度 | 功能全但不可定制 | 偏 PC | **原生嵌入(相机/通讯录/地图/GPS)** |
+| 事前→报销 | 关联控件 | 一键转换+预算冻结 | **一键导入+三级追踪+分批报销** |
+| 审批形态 | 基础 | 会签/或签/加签 | 取决于 ERP 审批引擎 |
+
+**已有竞争力**:预算冻结+三级使用追踪、外勤防伪(GPS只读+水印)、公告触达审计+DING催办、复合角色+工作台动态变体。
+
+**差距(部分取决于 ERP 能力)**:会签/或签审批形态、OCR 发票识别、离线审批。
+
+---
+
+## 8. 待确认
+
+| # | 事项 | 影响 |
+|---|------|------|
+| 1 | ERP 审批历史是按 instanceId 还是 bizType+bizId 查询? | 决定是否要 PreviousInstanceIds |
+| 2 | 各套 ERP 的具体 API 签名差异 | .NET 服务端适配器层开发量(审批+预算+项目+科目+成本+标准+客户+汇率) |
+| 3 | ERP 客户表的主键类型和查询 API | 外勤日志客户关联的实现方式 |
+| 4 | ERP 费用标准表结构 | StandardService 适配器的查询参数 |