# TBOSS OA 模块 — 全量 UI 设计

> 版本：v1.0 | 日期：2026-06-04 | 基于 PRD / Database / Strategy 文档
>
> **架构前提**：用户/组织/客户数据来自 ERP（通过 .NET API）、审批引擎复用 ERP、消息复用 .NET 服务端、OA 权限独立 ACL。
> 页面中标注「经理」「财务」「管理员」的区块为对应角色专属增量功能，基础内容对所有角色可见。

---

## 0. 页面跳转关系总图

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           Appshell — 底部三 Tab                               │
│                     /  →  IndexedStack + GoRouter                            │
└─────────────────────────────────────────────────────────────────────────────┘
         │                    │                    │
         ▼                    ▼                    ▼
    ┌─────────┐         ┌─────────┐         ┌─────────────┐
    │ /messages │         │  /home   │         │  /profile   │
    │ 消息通知  │         │ 工作台   │         │  个人中心    │
    └────┬─────┘         └────┬─────┘         └──────┬──────┘
         │                    │                      │
         │ 点击消息卡片        │ 轮播图点击             │ 语言设置 / 关于
         │ 按 BizType 路由    │ → 公告详情/外部链接     │
         ▼                    │                      │
    ┌─────────────────┐       │ 金刚区·发起            │
    │ 审批待办 → 详情页 │       │ → /expense-apply/apply │
    │ 审批结果 → 详情页 │       │ → /expense/apply      │
    │ 撤回通知 → 消息列表│       │ → /vehicle/apply      │
    │ 系统公告 → 公告详情│       │ → /overtime/apply     │
    │ 过期提醒 → 详情页  │       │ → /announcement/create│
    └─────────────────┘       │   (管理员额外)          │
                              │                        │
                              │ 金刚区·记录             │
                              │ → /expense-apply/list   │
                              │ → /expense/list         │
                              │ → /overtime/list        │
                              │ → /vehicle/list         │
                              │ → /outing-log/list      │
                              │ → /announcement/list    │
                              │                        │
                              │ 金刚区·报表             │
                              │ → /report/expense-apply │
                              │ → /report/expense       │
                              │ → /report/overtime      │
                              │ → /report/vehicle       │
                              │ → /report/outing-log    │
                              │                        │
                              │ 看板卡片                │
                              │ → 对应列表/报表/消息    │
                              └────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│                            表单 → 列表 → 详情 主链路                           │
│                                                                              │
│  ┌────────────┐    存草稿/提交     ┌────────────┐    点击卡片    ┌──────────┐ │
│  │  表单页     │ ───────────────→ │  列表页     │ ────────────→ │  详情页   │ │
│  │ (apply/    │                  │  (list)     │               │ (detail/ │ │
│  │  create)   │ ←─────────────── │             │ ←──────────── │  :id)    │ │
│  └────────────┘  左滑编辑/删除    └────────────┘   返回          └──────────┘ │
│       ↑                ↑              │                           │          │
│       │                │              │ 经理: 左滑一键同意          │          │
│       │                │              │ 用车: 左滑确认还车          │          │
│       │                │              │                           │          │
│       │          rejected 重新编辑 ───┘              draft → 编辑跳表单       │
│       │                                            pending → 撤回申请        │
│       └── draft 编辑回填 ──────────────────────→   approved → 还车/核销      │
│                                                                              │
│  费用申请: /expense-apply/apply → /expense-apply/list → /expense-apply/detail │
│  费用报销: /expense/apply       → /expense/list       → /expense/detail      │
│  加班申请: /overtime/apply      → /overtime/list      → /overtime/detail     │
│  用车申请: /vehicle/apply       → /vehicle/list       → /vehicle/detail      │
│  外勤日志: /outing-log/create   → /outing-log/list    → /outing-log/detail   │
│                                                                              │
│  特殊链路：                                                                   │
│  · 报销表单可「导入费用申请」→ 半屏多选已通过的申请 → 自动带入项目/科目         │
│  · 用车 approved 后 → 详情页底部「还车登记」→ Status=returned                  │
│  · 报销 approved+unpaid → 财务进详情页 → 三字查验 → 打款归档                   │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│                              公告 & 权限 独立链路                               │
│                                                                              │
│  管理员:                                                                      │
│  /home 金刚区 → /announcement/create → 存草稿/预览/发布                        │
│  /profile → /admin/permissions → 搜索员工 → 抽屉编辑权限 → 保存+审计日志       │
│                                                                              │
│  全员:                                                                        │
│  /announcement/list ──点击──→ /announcement/detail/:id                        │
│       ↑                          │                                            │
│       │                          ├ 停留≥2s → 自动标记已读                      │
│       │                          ├ 管理员: 已读/未读统计 + DING 催办           │
│       └──── 返回列表(红点消失) ───┘                                            │
└──────────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────────┐
│                              报表 独立链路                                      │
│                                                                              │
│  /home 金刚区·报表 ─┬─ /report/expense-apply-detail   费用申请明细报表          │
│                    ├─ /report/expense-detail           费用报销明细报表          │
│                    ├─ /report/overtime-detail          加班明细报表             │
│                    ├─ /report/vehicle-detail           用车明细报表             │
│                    └─ /report/outing-log-detail        外勤日志明细报表          │
│                                                                              │
│  每页: 时间筛选 + 专属筛选 → 数值卡片 + fl_chart 趋势图                          │
│  经理: 柱状图点击 → 下方明细列表联动过滤 → 可穿透跳详情页                         │
│  财务/管理员: 底部明细列表 + Excel 导出                                         │
└──────────────────────────────────────────────────────────────────────────────┘

角色路由速查：
  员工   → 轮播图 + 金刚区 + 个人快捷看板(本月报销+本月单据)
  经理   → 员工版 + 待审批红色角标 + 快捷看板升级为部门范围 + 下属审批切换
  财务   → 员工版 + 财务看盘(全公司:已支付/待付款/异常退回) + 核销入口
  管理员 → 员工版 + 金刚区额外「发布公告」+ 全公司财务看盘 + 权限管理入口
```

---

## 1. 框架、导航及权限模块（5 页）

#### 页面 1：Appshell 底部导航 (`/`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 底部导航 | TDTabBar | 三个 Tab：消息、工作台、我的 |
| 主内容区 | IndexedStack + GoRouter | 路由驱动的视图容器，保持各 Tab 页面状态 |
| 消息角标 | TDBadge | 未读消息数，进入时拉取 + 30s 轮询或 Push 刷新。数据来源 .NET 消息模块 API |

| 交互 | 行为 |
|------|------|
| 点击 Tab | 路由切换，图标变 `#00ABF3` |
| 重复点击当前 Tab | 列表页 ScrollToTop，非列表无行为 |

---

#### 页面 2：消息通知聚合页 (`/messages`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"消息通知"，右侧"全部已读"按钮（仅未读>0 时显示） |
| 列表 | TDListView | 下拉刷新 / 上拉加载 |
| 数据来源 | — | .NET 消息模块 API（非本地 Message 表） |

##### 消息类型

| 类型 | 图标 | 示例标题 | 点击行为 |
|------|------|---------|---------|
| 审批待办 | 📋 | "张三提交了差旅报销，待审批" | 进入详情页，底部展审批操作栏 |
| 审批结果 | ✅/❌ | "您的差旅报销已通过" / "已被拒绝" | 进入详情页查看结果 |
| 撤回通知 | ↩ | "张三撤回了差旅报销" | 消息列表（单据已不可审批） |
| 系统公告 | 📢 | "【通知公告】2026年端午放假安排" | 进入公告详情页 |
| 过期提醒 | ⏰ | "您的差旅申请已过期，预算已释放" | 进入对应详情页 |

##### 消息卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 图标 | Icon | 按消息类型展示不同图标 |
| 标题 | Text | 消息大标题 |
| 摘要 | Text | 摘要文本，单行截断 |
| 时间 | Text | 发送时间，格式 MM-DD HH:mm |
| 未读标记 | TDBadge | 未读消息显示红点 |

##### 卡片交互

| 操作 | 组件 | 行为 |
|------|------|------|
| 左滑 | TDSwipeAction | `[标记已读]`（蓝）+ `[删除]`（红）。已读消息不可左滑 |
| 标记已读 | — | 调 .NET 消息 API，成功→红点淡出+卡片透明度↓ |
| 删除 | TDDialog | 确认弹窗→卡片收起+调 API 删除 |

##### 推送通道

| 消息类别 | 通道 |
|---------|------|
| 审批类 | App Push + 站内消息 |
| 公告类 | 站内消息 |
| DING 催办 | App Push（高优先级，无视免打扰） |

---

#### 页面 3：工作台 (`/home`)

##### 角色判定

| 权限 | 工作台版本 | 优先级 |
|------|---------|:--:|
| 有 `oa.admin.*` | 管理员版 | 1 |
| 无 admin，有 `oa.expense.verify_invoice` | 财务版 | 2 |
| 无上述，有 `oa.*.approve` 或 `oa.report.view_dept` | 经理版 | 3 |
| 其他 | 员工版 | 4 |

> 高优先级叠加低优先级的基础区块。权限数据来源：`OaUserPermission` 本地表。

##### 所有角色共有

| 区域 | 组件 | 说明 |
|------|------|------|
| 轮播图 | TDesign `Swiper` | 3s 自动切换，循环播放。点击有 LinkUrl→跳转，无→全屏预览双指缩放。数据来源 `SysBanner`（OA 本地） |
| 金刚区 | TDGrid（4 列） | 三区域：**发起** 费用申请/费用报销/用车申请/加班申请；**记录** 申请记录/报销记录/加班记录/用车记录 + 外勤日志/公司公告；**报表** 费用申请报表/费用报销报表/加班报表/用车报表 + 外勤日志报表 |

##### 员工版

| 区域 | 组件 | 说明 |
|------|------|------|
| 快捷看板-卡片1 | TDCard | 本月累计报销（本人已付款总额 ¥），点击→报销记录列表 |
| 快捷看板-卡片2 | TDCard | 本月已提单据（本人提交总数 笔），点击→申请记录列表 |
| 数据来源 | — | .NET 服务端预计算，缓存 5min，下拉强制刷新 |

##### 经理/老板版

| 区域 | 组件 | 说明 |
|------|------|------|
| 待审批卡片 | TDCard + TDBadge | 红色角标显示待办数，数据来源 .NET → ERP，点击→消息列表 |
| 快捷看板升级 | TDCard ×3 | 数据范围升级为本部门：本月累计报销(部门) / 本月已提单据(部门) / 部门在途单据 |

##### 财务版

| 区域 | 组件 | 说明 |
|------|------|------|
| 财务看盘 | TDCard ×3 | 替代快捷看板，全公司数据：本月已支付总额 / 待付款总额 / 本周异常退回 |
| 数据来源 | — | .NET 全公司聚合查询，缓存 5min |

##### 管理员版

| 区域 | 组件 | 说明 |
|------|------|------|
| 金刚区增量 | TDGrid | 发起区域额外显示"发布公告"入口 |
| 快捷看板 | TDCard ×3 | 与财务版相同，全公司数据 |

---

#### 页面 3.1：个人中心 (`/profile`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| 头像 | TDAvatar | 点击更换，数据来源 `GET /api/user/{erpUserId}` |
| 用户信息 | Text | 姓名 / 部门 / 岗位，同上 |
| 功能列表 | TDListView | 分组样式 |

##### 功能列表

| 功能 | 行为 |
|------|------|
| 语言设置 | 底部弹出 TDActionSheet：简体中文 / 繁體中文 / English。选择后立即生效，写入本地存储，下次启动保持 |
| 关于 | 版本号 / 用户协议 / 隐私政策 |

##### 交互

| 操作 | 行为 |
|------|------|
| 点击头像 | 唤起相册/相机 → 裁剪(1:1,≤2MB) → `PUT /api/user/avatar` → 全局刷新。失败 Toast"头像上传失败" |
| 登录态 | 宿主 App 统一管理。监听宿主登出→清本地缓存。API 401→MethodChannel 通知宿主重登录 |

---

#### 页面 3.2：权限管理 (`/admin/permissions`)【管理员专属】

| 区域 | 组件 | 说明 |
|------|------|------|
| 搜索栏 | TDSearchBar | 按姓名/工号模糊搜索，300ms 防抖，来源 `GET /api/user/search` |
| 员工列表 | TDListView | 每页 20 条，默认按部门+姓名排序。每张卡片展示：头像、姓名、工号、部门、当前权限标签（TDTag） |

**抽屉内容**（TDDrawer，点击员工卡片右侧滑出）：

| 区域 | 组件 | 说明 |
|------|------|------|
| 快捷套餐 | TDButton ×4 | 员工（oa.*.apply + oa.*.view_own + oa.outing_log.create）/ 审批人（员工权限 + oa.*.approve + oa.report.view_dept + oa.outing_log.comment）/ 财务（oa.*.view_all + oa.expense.verify_invoice + oa.report.export）/ 管理员（全部）。点击后自动勾选对应权限点 |
| 权限点列表 | TDCheckbox 分组 | 按模块分组（报销/事前/加班/用车/外勤/公告/报表/管理），逐项勾选。无角色用户默认等同"员工" |
| 确认保存 | TDButton | `PUT /api/oa/user-permissions` → 写入 OaPermissionChangeLog（Before/After 快照 JSON）→ Toast"权限已更新"。失败→Toast 红 |
| 变更记录 | 折叠区 | TDTimeline 展示最近 20 条 OaPermissionChangeLog：操作时间、操作人、变更摘要（如"添加了财务人员角色"） |
| 自保护 | — | 无法取消自己的 admin 角色→Toast；至少保留一名管理员→后端拒绝 |

---

## 2. 费用控制与报销模块（6 页）

#### 页面 4：费用申请表单 (`/expense-apply/apply`)

##### 基本信息区

| 字段 | 组件 | 说明 |
|------|------|------|
| 申请人 | TDInput（只读） | 当前登录用户姓名，`GET /api/user/{erpUserId}` |
| 所属部门 | TDInput（只读） | 同上 |
| 申请日期 | TDInput（只读） | 当前日期 `DateTime.now()`，格式 YYYY-MM-DD |
| 紧急程度 | TDRadioGroup | 普通 / 紧急 / 特急，默认普通 |
| 申请事由 | TDTextarea | 必填，≤500 字，对应 DB `Purpose` |
| 有效期至 | TDDatePicker | 可选，不填则永不过期，对应 DB `ValidUntil` |
| 关联单号 | TDInput | 关联的外部单号（合同号/询价单号），用于采购类申请追溯，非必填，对应 DB `ReferenceNo` |
| 备注 | TDTextarea | 选填，≤500 字，对应 DB `Remark` |

##### 预估明细区

| 字段 | 组件 | 说明 |
|------|------|------|
| 费用类别 | TDPicker | SysCostCategory 叶子节点 |
| 会计科目 | TDPicker | .NET → ERP SubjectService |
| 关联项目 | TDPicker | .NET → ERP ProjectService |
| 费用部门 | TDPicker | 费用承担部门 |
| 预计开始日期 | TDPicker | |
| 预计结束日期 | TDPicker | |
| 预估金额 | TDInput（数字） | >0，对应 DB `EstimatedAmount` |
| 费用说明 | TDTextarea | 选填，≤500 字 |
| [+ 添加] | 按钮 | 平滑展开新明细卡片 |
| ✕ 删除 | 图标按钮 | 气泡确认→折叠消失 |
| — | — | — |
| **预估总金额** | 只读（大字） | 所有明细行预估金额自动汇总为"¥XX,XXX.XX"，对应 DB `EstimatedAmount`。若超预算→金额变红 + 警告"您的申请金额已超支，提交后将自动触发高管特批流程" |

##### 附件上传区

| 属性 | 说明 |
|------|------|
| 组件 | TDesign `TDUpload` |
| 数量 | 最多 9 张，满 9 隐藏上传入口 |
| 类型 | 图片/PDF/Word/Excel ≤20MB |
| 来源 | 相册 / 相机 / 文件选择器 |
| 展示 | 网格缩略图，右上角删除按钮 |
| 预览 | 点击缩略图→黑底全屏（双指缩放 0.5x~3x），PDF→原生查看器 |
| 存储 | `Attachment (BizType='expense_apply')` |

##### 底部操作栏

| 按钮 | 颜色 | 可见条件 | 行为 |
|------|------|---------|------|
| `[重置]` | 灰 | 编辑已有草稿时 | 确认弹窗→清空至初始状态 |
| `[存为草稿]` | 浅蓝 | 始终 | 不校验，保存所有已填内容（含空字段） |
| `[提交审批]` | `#00ABF3` | 始终 | 触发全局校验，失败不提交，超预算不阻止 |

##### 校验规则

| 校验项 | 规则 |
|--------|------|
| 费用类型 | 必填 |
| 费用事由 | 必填，≤500 字 |
| 项目 / 科目 | 必选（ERP 有数据时） |
| 预估明细 | 至少一行（预估金额>0） |

> 不通过时 ScrollTo 报错字段（300ms）+ 红闪 2s + TDMessage 红色横幅。

##### 页间流转

| 操作 | API | 成功 | 失败 |
|------|-----|------|------|
| 存草稿 | `PUT /api/oa/expense-apply/draft` | Toast 绿"已保存为草稿" → 跳列表（激活"草稿"Tab） | Toast 红"保存失败，请稍后重试" |
| 返回（有修改） | — | TDDialog"当前内容尚未保存，是否退出？"→ 继续编辑 / 放弃并退出 | — |
| 提交审批 | `POST /api/oa/expense-apply/submit` | .NET 创建 ERP 审批实例 → 返回 ApprovalInstanceId → Toast"已提交，等待审批" → 跳列表 | 同上 |

---

#### 页面 5：费用报销表单 (`/expense/apply`)

##### 新建方式

| 方式 | 操作 | 说明 |
|------|------|------|
| 导入申请 | 点击顶部蓝色快捷链「导入已通过的费用申请」| 半屏抽屉多选 approved+有额度的申请，每条填导入金额。项目/科目自动带入，额度实时校验。空→TDEmpty |
| 直接新建 | 不导入，手动填写 | 手动选择项目→科目→查预算，链式操作与页面 4 相同 |

##### 基本信息区

| 字段 | 组件 | 说明 |
|------|------|------|
| 报销日期 | TDInput（只读） | 提交时写入当日 |
| 报销单号 | TDInput（只读） | 草稿时显示"保存后自动生成"，提交后显示 BX-YYYYMMDD-XXX |
| 币别 | TDPicker | 默认 CNY，可选 USD/EUR/JPY/HKD/GBP，选外币时明细行自动联动汇率 |
| 报销人员 | TDInput（只读） | 当前登录用户姓名 |
| 报销部门 | TDInput（只读） | 当前用户所属部门 |
| 支付方式 | TDPicker | 银行转账 / 现金 / 支付宝 / 微信支付，默认银行转账 |
| 凭证号码 | TDInput | 选填，财务核销时录入的金蝶记账凭证号 |
| 备注 | TDInput | 选填，整单备注说明 |

##### 关联管控区

| 字段 | 组件 | 说明 |
|------|------|------|
| 报销事由 | TDTextarea | 必填，≤500 字 |
| 关联项目 | TDCascader | 同页面 4 |
| 预算科目 | TDPicker | 同页面 4 |
| 报销金额合计 | 只读 | ∑Detail.TotalAmount 自动汇总 |
| 核准金额合计 | 只读 | ∑Detail.ApprovedAmount 自动汇总，审批阶段由审批人修改 |

##### 收款账户区

| 字段 | 组件 | 说明 |
|------|------|------|
| 开户行 | TDInput+下拉联想 | `GET /api/dict/banks`，可自由输入 |
| 户名 | TDInput | 默认当前用户姓名，可修改 |
| 账号 | TDInput | 校验 16-19 位数字 |

##### 报销明细区

| 字段 | 组件 | 说明 |
|------|------|------|
| 费用项目 | TDInput | 费用摘要描述，必填 |
| 金额（含税） | TDInput（数字） | 价税合计，必填。输入后自动计算不含税金额 |
| 金额（不含税） | 只读 | = 含税金额 / (1+税率) |
| 核准金额 | TDInput（数字） | 默认等于含税金额，审批阶段审批人可修改 |
| 摘要 | TDInput | 选填，费用补充说明 |
| 客户/厂商 | TDInput | 选填，采购/招待类费用关联的企业名称 |
| 费用科目 | TDPicker | SysCostCategory 叶子节点 |
| 项目代号 | 只读 | 由关联项目自动带入 |
| 科目代号 | 只读 | 由关联科目自动带入 |
| 项目类别 | 只读 | 由关联项目自动带入 |
| 已充金额 | TDInput（数字） | 预付/借款冲抵金额，默认 0 |
| 税金 | 只读 | = 含税金额 - 不含税金额 |
| 税率 | TDTag 选择 | 6% / 9% / 13%，必选 |
| [+ 添加] / ✕ 删除 | 按钮 | 同页面 4 |

##### 附件上传区

| 属性 | 说明 |
|------|------|
| 组件 | TDesign `TDUpload` |
| 数量 | 最多 9 张 |
| 大小 | ≤20MB |
| 绑定 | 可绑定到具体明细行（`Attachment.DetailId`） |
| 存储 | `Attachment (BizType='expense')` |
| 交互 | 同页面 4 |

##### 底部操作栏

| 按钮 | 颜色 | 可见条件 | 行为 |
|------|------|---------|------|
| `[重置]` | 灰 | 编辑已有草稿时 | 确认弹窗→清空至初始状态 |
| `[存为草稿]` | 浅蓝 | 始终 | 不校验，保存已填内容 |
| `[提交审批]` | `#00ABF3` | 始终 | 触发全局校验，失败不提交，超预算不阻止 |

##### 校验规则

| 校验项 | 规则 |
|--------|------|
| 报销事由 | 必填 |
| 项目 / 科目 | 直接新建时必选（ERP 有数据时） |
| 导入金额 | 每条>0，∑≤对应申请剩余可报额度 |
| 收款银行/户名/账号 | 必填，账号 16-19 位数字 |
| 明细行 | 至少一行（含税金额>0，费用项目必填） |
| 发票信息 | 发票类型必选，专票/普票时发票号必填 |

> 不通过时 ScrollTo 报错字段+红闪+TDMessage。

##### 页间流转

| 操作 | API | 成功 | 失败 |
|------|-----|------|------|
| 存草稿 | `PUT /api/oa/expense/draft` | Toast 绿"已保存为草稿" → 跳列表（激活"草稿"Tab） | Toast 红 |
| 提交审批 | `POST /api/oa/expense/submit` | .NET 创建 ERP 审批实例 + 冻结预算 + 写入 ExpenseApplyMapping → Toast"已提交" → 跳列表 | Toast 红 |
| 返回（有修改） | — | TDDialog"是否退出？"→ 继续编辑 / 放弃 | — |

---

#### 页面 6：费用申请列表 (`/expense-apply/list`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"申请记录" |
| 筛选栏 | DateRangeFilter + FilterDropdown ×2 | TabBar 下方：日期范围选择器 + 费用类型（差旅/招待/办公/会议）+ 紧急程度（普通/紧急/特急）。筛选条件变化时重新拉取数据 |
| 状态筛选 | TDTabBar | Tab：全部 / 草稿 / 审批中 / 已通过 / 已拒绝 / 已撤回。默认选中"全部" |
| 列表 | TDListView | 下拉刷新（page=1）/ 上拉加载（page++）。数据来源 OA 本地 ExpenseApply 表，审批状态通过 .NET→ERP 实时查询 |
| 空状态 | TDEmpty | 文案随 Tab 变化："暂无草稿""暂无审批中的单据"等 |

##### 列表卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 编号 | Text | 单据编号 BXSQ-YYYYMMDD-XXX |
| 摘要 | Text | 事由摘要，单行截断 |
| 金额 | Text | 预估金额，粗体高亮 |
| 日期 | Text | 提单日期 YYYY-MM-DD |
| 类型标签 | TDTag | 费用类型标签（如"差旅"+"办公"） |
| 状态 | StatusTag | 灰-草稿 / 橙-审批中 / 绿-已通过 / 红-已拒绝 / 灰-已撤回 |

##### 卡片交互

| 状态/角色 | 操作 | 行为 |
|----------|------|------|
| 任意 | 点击 | → 详情页 `/expense-apply/detail/:id` |
| 本人 draft | 左滑 | `[编辑]`（TDSwipeAction 蓝）+ `[删除]`（红）。编辑→跳表单回填；删除→TDDialog 确认→软删除 |
| 本人 rejected | 左滑 | `[编辑]`（TDSwipeAction 蓝），跳表单回填 |
| 他人/其他 | 左滑无反应 | — |

##### 经理增量

| 区域 | 组件 | 说明 |
|------|------|------|
| 范围切换 | TDChip ×2 | TDTabBar 上方：`我的发起` / `下属审批`，选中高亮 `#00ABF3` |
| 下属卡片-申请人 | Text | 额外显示"申请人姓名 — 部门" |
| 下属 pending | TDSwipeAction | 左滑 `[一键同意]`（绿），就地审批 |

> 数据来源：`GET /api/oa/approval/subordinates` → OA 业务表 JOIN ERP 审批状态。

---

#### 页面 7：费用报销列表 (`/expense/list`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"报销记录" |
| 筛选栏 | DateRangeFilter | TabBar 下方：日期范围选择器。筛选条件变化时重新拉取数据 |
| 状态筛选 | TDTabBar | Tab：全部 / 草稿 / 审批中 / 已通过 / 待付款 / 已付款 / 已拒绝 / 已撤回 |
| 列表 | TDListView | 同页面 6。数据来源 OA 本地 Expense 表 |
| 空状态 | TDEmpty | 同页面 6 |

##### 列表卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 编号 | Text | 单据编号 BX-YYYYMMDD-XXX |
| 摘要 | Text | 事由摘要 |
| 金额 | Text | 报销金额，粗体高亮 |
| 日期 | Text | 提单日期 |
| 状态 | StatusTag | 同页面 6 |
| 付款状态 | TDTag | 黄-待付款 / 蓝-已付款 |

> 卡片交互同页面 6（左滑编辑/删除规则一致）。

##### 财务增量

| 区域 | 组件 | 说明 |
|------|------|------|
| 数据范围 | — | 默认全公司（不限制 ApplicantId） |
| 核销入口 | — | 点击卡片→详情页完成核销。列表页不提供快捷付款 |

##### 经理增量

与页面 6 相同：TDTabBar 上方 `TDChip` 范围切换 + `TDSwipeAction` 一键同意。

---


#### 页面 8：费用申请详情 (`/expense-apply/detail/:id`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 状态横幅 | StatusBanner | 审批中（⏳ 橙，显示当前审批人）/ 已通过（✅ 绿）/ 已拒绝（❌ 红）/ 已撤回（↩ 灰）。审批状态通过 ERP 实时查询 |
| 基本信息 | TDDescription | key-value 双栏：申请人 / 部门 / 日期 / 紧急程度 / 费用类型（TDTag 多选标签）/ 含税标记 / 有效期至 / 关联合同号 |
| 使用状态 | TDTag | 未使用（绿）/ 部分使用（黄）/ 已用完（灰），对应 DB `UsageStatus` |
| 关联项目 | TDDescription | 项目名称 / 预算科目 / 预估总金额（¥） |
| 明细列表 | TDTable | 每行：费用类别 / 预估金额 / 说明。>5 行默认折叠，`[展开全部]` 按钮展开 |
| 附件 | TDUpload | 支撑材料宫格，点击缩略图→全屏预览（双指缩放）。来源于 Attachment (BizType='expense_apply') |
| 审批时间线 | TDTimeline | 纵向时间线，首节点"发起人已提交"（拼装 CreateTime+申请人），后续节点来自 .NET→ERP。若 withdrawn→末尾追加灰色撤回节点 |
| 底部 | 见下方操作表 | 员工/经理/管理员角色操作 |


##### 页面 8 操作表

**员工**

| 状态 | 按钮 | 行为 |
|------|------|------|
| draft | `[编辑]` `[提交审批]` | 编辑→跳表单回填；提交→校验→调 ERP |
| pending | `[撤回申请]` | TDDialog→`POST /api/oa/approval/withdraw`→Status='withdrawn' |
| rejected | `[重新编辑并发起]` | 跳表单编辑态，提交后更新原记录 |
| approved | 无 | 等待报销引用 |
| withdrawn | 无 | 仅查看 |

**经理**（pending+当前审批人=该经理）：`[同意]`（主色）/ `[拒绝]`（白底红字，≥5字理由）。风控看板：申请人同类单据数+预算进度条。

**管理员**（所有状态）：`[强制终止]`（红底白字）+ `[查看审批链]`（含旧实例）。

> 通用交互：时间线节点点击→当前用户待审批可弹出审批操作。审批人头像/姓名点击→TDDialog。
---

#### 页面 9：费用报销详情 (`/expense/detail/:id`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 状态横幅 | StatusBanner | 审批状态（同页面 8）+ 付款状态 TDTag：待付款（黄）/ 已付款（蓝） |
| 基本信息 | TDDescription | 申请人 / 部门 / 日期 / 事由 / 项目 / 科目 / 报销总金额（¥） |
| 收款信息 | TDDescription | 开户行 / 户名 / 账号（详情页完整展示，列表页脱敏展示后 4 位 `****0123`） |
| 申请来源 | TDList | 若导入：列出关联申请编号 + 每张导入金额；若直接新建：不展示 |
| 明细列表 | TDTable | 每行：日期 / 类别 / 描述 / 原币金额+币种 / 税额 / 价税合计 / 本币金额+汇率 / 发票类型/号/码/税率。>5 行折叠 |
| 附件 | TDUpload | 发票影像宫格，可绑定明细行。来源于 Attachment (BizType='expense') |
| 审批时间线 | TDTimeline | 同页面 8 |
| 财务查验区 | TDCheckbox ×3 | 仅财务可见：验真 / 税号一致 / 类目合规。全勾→解锁打款按钮。对应 DB `IsInvoiceVerified/IsTaxIdMatched/IsCategoryCompliant` |
| 底部 | 见本页下方操作表 | 员工/经理/管理员/财务角色 |

##### 页面 9 操作表

**员工**

| 状态 | 按钮 | 行为 |
|------|------|------|
| draft | `[编辑]` `[提交审批]` | 编辑→跳表单回填；提交→校验→调 ERP |
| pending | `[撤回申请]` | TDDialog→`POST /api/oa/approval/withdraw`→Status='withdrawn' |
| rejected | `[重新编辑并发起]` | 跳表单编辑态，提交后更新原记录 |
| approved | 无 | 等待财务打款 |
| withdrawn | 无 | 仅查看 |

**经理**（pending+当前审批人=该经理）：`[同意]`（主色）/ `[拒绝]`（白底红字，≥5字理由）。风控看板：申请人同类单据数+预算进度条。

**管理员**（所有状态）：`[强制终止]`（红底白字）+ `[查看审批链]`（含旧实例）。

> 通用交互：时间线节点点击→当前用户待审批可弹出审批操作。审批人头像/姓名点击→TDDialog。

**财务**（Status=approved + PaymentStatus=unpaid）

| 操作 | 行为 |
|------|------|
| 三项合规复选框 | 验真 / 税号一致 / 类目合规。未全勾→`[确认打款]` 灰色锁定 |
| `[确认打款并归档]` | 全勾后激活→滑出凭证表单（电汇流水号+记账凭证号必填）→PaymentStatus='paid' |
| `[退回修改]` | 选择退回节点（员工重填/经理重审）→状态倒流 |
| 归档后 | 按钮替换为 `[下一笔待付款]`；无则 Toast"全部待付款单据已处理完毕" |

---

## 3. 勤务考勤、用车调度与外勤外务模块（9 页）

#### 页面 10：加班申请表单 (`/overtime/apply`)

##### 基本信息区

| 字段 | 组件 | 说明 |
|------|------|------|
| 加班类型 | TDPicker | 工作日 / 休息日 / 法定节假日，默认"工作日"。节假日标注费率（300%） |
| 补偿方式 | TDRadio | 转调休 / 结算加班费 / 混合模式。默认"转调休" |
| 混合比例 | TDSlider | 10%~90%，步长 10%，仅补偿方式=混合时显示。右侧实时文字："30% 转调休 + 70% 结算加班费" |
| 开始时间 | TDDatePicker | 年月日时分，必填 |
| 结束时间 | TDDatePicker | 年月日时分，必填。校验：必须晚于开始时间 |
| 净工时 | 只读大字卡片 | 自动计算 = (结束-开始) - 午餐(12:00-13:00) - 晚餐(18:00-18:30)。≤0→变红+提交按钮置灰 |
| 加班原因 | TDTextarea | 必填 |

##### 底部操作栏

| 按钮 | 行为 |
|------|------|
| `[存为草稿]` | 不校验，保存已填内容 |
| `[提交审批]` | 触发校验（必填项+净工时>0），通过后 `POST /api/oa/overtime/submit` → ERP 创建审批实例 |

##### 校验规则

| 校验项 | 规则 |
|--------|------|
| 开始/结束时间 | 必填，结束>开始 |
| 净工时 | 必须>0 |
| 加班原因 | 必填 |

> 不通过时 ScrollTo+红闪+TDMessage。

---

#### 页面 11：加班申请列表 (`/overtime/list`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"加班记录" |
| 筛选栏 | DateRangeFilter + FilterDropdown | TabBar 下方：日期范围选择器 + 加班类型（工作日/休息日/节假日） |
| 状态筛选 | TDTabBar | 全部 / 草稿 / 审批中 / 已通过 / 已拒绝 / 已撤回 |
| 列表 | TDListView | 下拉刷新/上拉加载，数据来源 OA 本地 Overtime 表，审批状态通过 ERP 实时查询 |

##### 列表卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 编号 | Text | JB-YYYYMMDD-XXX |
| 类型标签 | TDTag | 工作日/休息日/节假日 |
| 净工时 | Text | 粗体高亮 X.Xh |
| 补偿方式 | Text | 加班费 / 转调休 / "X%调休+Y%结算" |
| 日期 | Text | 提单日期 |
| 状态 | StatusTag | 同页面 6 |

##### 卡片交互

| 状态/角色 | 操作 | 行为 |
|----------|------|------|
| 任意 | 点击 | → 详情页 `/overtime/detail/:id` |
| 本人 draft | 左滑 | `[编辑]` `[删除]`（同页面 6） |
| 本人 rejected | 左滑 | `[编辑]` |
| 他人/其他 | 左滑无反应 | — |

> **经理增量**：TDChip 范围切换（`我的发起` / `下属审批`）+ TDSwipeAction `[一键同意]`。同页面 6。

---

#### 页面 12：加班申请详情 (`/overtime/detail/:id`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 状态横幅 | StatusBanner | 同页面 8 |
| 基本信息 | TDDescription | 申请人/部门/加班类型/补偿方式/混合比例/开始时间/结束时间/净工时/原因 |
| 审批时间线 | TDTimeline | 同页面 8 |
| 底部操作栏 | 同页面 8 | 员工端各状态操作。**经理端**（pending 审批人=本人）：同意/拒绝。**管理员端**：强制终止+查看审批链 |

---

#### 页面 13：用车申请表单 (`/vehicle/apply`)

##### 基本信息区

| 字段 | 组件 | 说明 |
|------|------|------|
| 车牌号 | TDPicker | OA 本地 SysVehicle 车池（仅显示空闲+使用中车辆）。冲突车辆标红，无可用→TDEmpty |
| 用车事由 | TDInput | 必填 |
| 用车目的 | TDPicker | 客户接待 / 商务出行 / 公务 |
| 始发地 | TDInput | 原生定位自动填入，可修改 |
| 目的地 | TDInput + 地图图标 | 点击地图图标→MethodChannel 唤醒原生地图选点，回填地址+经纬度 |
| 出车时间 | TDDatePicker | 年月日时分，必填 |
| 还车时间 | TDDatePicker | 年月日时分，必填。校验：必须晚于出车时间 |
| 同行人数 | TDInput（数字） | 默认 1，输入 0 自动调整为 1 |
| 同行人 | TDChip + MethodChannel | 点击[+]→原生通讯录多选→胶囊标签展示，x 剔除 |

##### 排期冲突检测

| 触发条件 | 行为 |
|---------|------|
| 车牌+出车/还车时间选定后 | 异步调 .NET → OA 本地 Vehicle 表校验时段重叠 |
| 冲突 | 红色警告"该时段车辆已被预订"，提交按钮置灰。提示"请选择其他车辆或调整时间" |
| 无冲突 | 可正常提交 |

##### 底部操作栏

与页面 4 相同：`[存为草稿]` + `[提交审批]`，提交调 `POST /api/oa/vehicle/submit`。

##### 校验规则

| 校验项 | 规则 |
|--------|------|
| 车牌号 | 必选 |
| 事由/目的 | 必填 |
| 出车/还车时间 | 必填，还车>出车 |
| 排期冲突 | 无冲突方可提交 |

---

#### 页面 14：用车申请列表 (`/vehicle/list`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"用车记录" |
| 筛选栏 | DateRangeFilter + FilterDropdown | TabBar 下方：日期范围选择器 + 用车目的（客户接待/商务出行/公务） |
| 状态筛选 | TDTabBar | 全部 / 草稿 / 审批中 / 已通过 / 已拒绝 / 已撤回 / 已还车 |
| 列表 | TDListView | 数据来源 OA 本地 Vehicle 表 |

##### 列表卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 编号 | Text | YC-YYYYMMDD-XXX |
| 车牌 | Text | 粗体高亮 |
| 行程 | Text | 始发地 → 目的地 |
| 时间 | Text | 出车时间 MM-DD HH:mm |
| 用途标签 | TDTag | 接待/商务/公务 |
| 状态 | StatusTag | 同页面 6，额外含 returned（已还车-蓝） |

##### 卡片交互

| 状态/角色 | 操作 | 行为 |
|----------|------|------|
| 任意 | 点击 | → 详情页 `/vehicle/detail/:id` |
| 本人 draft/rejected | 左滑 | `[编辑]` `[删除]`（同页面 6） |
| 本人 approved | 左滑 | `[确认还车]`，拉起半屏核销抽屉 |
| 已还车 | 左滑无反应 | — |

> 经理增量：TDChip 范围切换 + TDSwipeAction 一键同意（同页面 6）。

---

#### 页面 15：用车申请详情 (`/vehicle/detail/:id`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 状态横幅 | StatusBanner | 同页面 8，额外含 returned（已还车-蓝） |
| 基本信息 | TDDescription | 申请人/部门/车牌/目的/事由/始发地→目的地/出车时间/还车时间/同行人数/同行人列表 |
| 地图 | 静态地图 | 始发地+目的地标记，点击→MethodChannel 唤醒原生地图导航 |
| 审批时间线 | TDTimeline | 同页面 8 |
| 底部操作栏 | 同页面 8 | 员工端各状态操作 |

##### 还车登记（仅 approved 状态）

| 字段 | 组件 | 说明 |
|------|------|------|
| 实还时间 | TDDatePicker | 年月日时分。若早于 EndTime→灰字提示"提前还车"；若晚于→红字"已超出原计划还车时间" |
| 出车前里程 | TDInput（数字） | 必填 |
| 还车后里程 | TDInput（数字） | 必填，校验≥出车前里程。不通过→红色提示+提交按钮置灰 |
| 实际费用金额 | TDInput（数字） | 路桥费/停车费等实际费用总额（元），对应 DB `ActualCost` |
| 费用备注 | TDInput | 费用明细补充说明，对应 DB `CostRemark` |
| 确认提交 | 按钮 | TDDialog"提交后里程和费用不可再修改"→确认→Status='returned'→页面刷新+底部灰字"已还车归档于 YYYY-MM-DD HH:mm" |

> **经理端**（pending 审批人=本人）：详情页底部同意/拒绝操作栏。**管理员端**：强制终止+查看审批链。

---

#### 页面 16：外勤日志创建 (`/outing-log/create`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| GPS 定位区 | MethodChannel → 只读文本 | 页面初始化强制请求高精度 GPS，逆地理编码地址只读不可修改。绿色盾牌安全图标。精度>100m→黄色警告。失败→TDEmpty"无法获取当前位置"+提交按钮置灰 |
| 客户名称 | TDInput + 联想 | `GET /api/customer/search?q=` 联想匹配 ERP 客户池。无匹配→自由文本，提交时 .NET 自动创建 ERP 客户 |
| 客户联系人 | TDPicker | 选填，选定客户后加载该客户的联系人列表（来源 ERP 客户主数据），对应 DB `ContactId` |
| 工作总结 | TDTextarea | 必填 |
| 后续计划 | TDInput | 选填 |

##### 现场拍照

| 属性 | 说明 |
|------|------|
| 方式 | 跳过相册，MethodChannel 直接唤起原生相机 |
| 水印 | 服务器授时 + GPS 经纬度，自动渲染 |
| 数量 | 最少 1 张，最多 9 张 |
| 存储 | Attachment (BizType='outing_log')，FileType='sign_in_photo' |
| 校验 | <1 张→提交按钮置灰+红提示"请至少拍摄一张现场照片"。相机权限被拒→TDDialog+前往设置 |

##### 底部操作栏

| 按钮 | 行为 |
|------|------|
| `[存为草稿]` | GPS/CustomerId 可为空，保存已填内容 |
| `[提交]` | 校验 GPS 已定位 + 客户名已填 + 照片≥1。提交调 `PUT /api/oa/outing-log/submit` |

---

#### 页面 17：外勤日志列表 (`/outing-log/list`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"外勤记录" |
| 筛选栏 | DateRangeFilter | TabBar 下方：日期范围选择器 |
| 筛选 | TDTabBar | 全部 / 草稿 / 已完成 |
| 列表 | TDListView | 按 CreateTime 倒序，数据来源 OA 本地 OutingLog 表 |

##### 列表卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 编号 | Text | VST-YYYYMMDD-XXX |
| 客户名 | Text | 粗体高亮 |
| 地址 | Text | 签到地址截断一行 |
| 摘要 | Text | 工作总结截取 50 字 |
| 日期 | Text | YYYY-MM-DD |
| 状态 | StatusTag | 草稿（灰）/ 已完成（绿） |
| 新点评标记 | TDBadge | 橙色，条件：`Comment.CreateTime > COALESCE(LastViewedTime,'1900-01-01')` 且评论者≠本人 |

##### 卡片交互

| 状态/角色 | 操作 | 行为 |
|----------|------|------|
| 任意 | 点击 | → 详情页 `/outing-log/detail/:id` |
| 本人 draft | 左滑 | `[编辑]` `[删除]` |
| completed | 左滑无反应 | — |

> 经理增量：TDChip 范围切换（同页面 6），卡片追加业务员姓名。

---

#### 页面 18：外勤日志详情 (`/outing-log/detail/:id`)

| 区域 | 组件 | 说明 |
|------|------|------|
| 微缩地图 | 静态地图 | 点击→MethodChannel 唤醒原生导航 |
| 基本信息 | TDDescription | 业务员/部门/客户名/签到地址/签到时间 |
| 工作总结 | Text | 完整正文 |
| 后续计划 | Text | 后续推进计划 |
| 照片墙 | TDUpload | 带防伪水印照片，点击全屏预览 |
| 点评区 | TDList | 按时间升序气泡样式展示。员工端只读 |
| 底部（经理） | TDRate + TDInput | 星级评分 1-5 星 + 文字点评 + 发送按钮 |

##### 页面交互

| 操作 | 行为 |
|------|------|
| 进入详情 | 无感 `PUT /api/oa/outing-log/:id/view`，更新 LastViewedTime，列表"新点评"红点消失 |
| 经理点评 | 【经理专属】点选星级+输入文字→发送→气泡追加。写入 OutingLogComment，同步更新 OutingLog.UpdateTime |

---

## 4. 行政公告模块（3 页）

#### 页面 19：公告列表 (`/announcement/list`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"公司公告" |
| 类型筛选 | TDTabBar | 全部 / 通知公告 / 人事与制度 / 放假与活动。管理员额外显示"我的草稿"Tab |
| 列表 | TDListView | 排序：置顶优先→未过期按发布时间倒序→已过期置灰。数据来源 OA 本地 Announcement 表 |
| 空状态 | TDEmpty | "暂无行政公告" |

##### 列表卡片

| 元素 | 组件 | 说明 |
|------|------|------|
| 标题 | Text | 粗体 |
| 类型标签 | TDTag | 通知/制度/活动 |
| 部门 | Text | 发布部门（调 `GET /api/user/{publisherId}`） |
| 时间 | Text | MM-DD HH:mm |
| 未读标记 | TDBadge | 红点，AnnouncementReadLog.IsRead=0 时显示 |
| 已过期 | Text | 卡片整体置灰 + 标题末尾标注"已过期" |

> 不支持搜索。点击卡片→详情页。返回列表后红点消失。

---

#### 页面 20：公告详情 (`/announcement/detail/:id`)

##### 页面结构

| 区域 | 组件 | 说明 |
|------|------|------|
| 标题 | Text | 大标题，红头文件样式 |
| 发布信息 | Text | 发布部门 / 发布时间 |
| 正文 | WebView | HTML/Markdown 渲染 |
| 附件 | TDList | 点击→原生下载管理器打开。空→整区隐藏。来源 Attachment (BizType='announcement') |
| 已过期横幅 | TDBanner | 红色"该公告已于 YYYY-MM-DD HH:mm 过期" |

##### 页面交互

| 操作 | 行为 |
|------|------|
| 停留≥2s | 无感 `POST /api/oa/announcement/:id/read`，AnnouncementReadLog.IsRead=1 |
| <2s 返回 | 不发送已读请求，红点保留 |
| 返回列表 | 已读公告红点消失 |

##### 管理员增量

| 区域 | 组件 | 说明 |
|------|------|------|
| 已读/未读统计 | TDChip ×2 | `[已读 N 人]`（绿）/ `[未读 N 人]`（灰）。点击展开员工列表（头像+部门） |
| DING 催办 | TDButton | 点击→震动反馈→封装未读 UserIds→MethodChannel 强推 Push。Toast"已向 N 名未读员工发送催办通知"。更新 `IsUrged=1` |

---

#### 页面 21：公告发布 (`/announcement/create`)【管理员专属】

| 区域 | 组件 | 说明 |
|------|------|------|
| 导航栏 | TDNavbar | 标题"发布公告"，右侧 `[预览]` 按钮 |
| 标题 | TDInput | 必填 |
| 分类 | TDPicker | notice(通知)/policy(制度)/activity(活动) |
| 正文 | TDRichText | 加粗/斜体/下划线/列表/图片/链接/字号 H1-H3 |
| 附件 | TDUpload | 最多 9 个，PDF/图片/Word/Excel，单文件≤20MB |
| 置顶 | TDSwitch | 默认关闭，对应 DB `IsTop` |
| 有效期 | TDDatePicker | 可选，不填永不过期，对应 DB `ExpiryDate` |
| 接收范围 | TDDrawer | 点击→右侧滑出：①全员（默认）②按部门（部门树多选，TDCheckbox）③按指定用户（员工搜索多选）。底部实时统计覆盖人数。对应 DB `PrivateLevel` + `AnnouncementTarget` |

| 按钮 | 行为 |
|------|------|
| `[存为草稿]` | Status='draft'，仅创建者+管理员可见 |
| `[预览]` | TDDialog 全屏模拟详情页效果 |
| `[发布]` | TDDialog 确认→Status='published'→异步写入 AnnouncementReadLog→Toast |

---

## 5. 报表模块（5 页）

> 入口：工作台金刚区报表区域（5 个独立入口）。每个报表独立页面，独立路由，独立加载数据。筛选条件在页面顶部，下方为数值卡片 + 趋势图（fl_chart）。角色数据范围：员工=本人、经理=本部门、财务/管理员=全公司。预置时间段缓存 5min，自定义时间段实时查询。

---

#### 页面 22：费用申请明细报表 (`/report/expense-apply-detail`)

**页面结构**

| 区域 | 组件 | 说明 |
|------|------|------|
| 时间筛选 | TDChip + TDDatePicker | 快捷按钮（本月/本季/本年）+ 自定义起止日期 |
| 状态筛选 | TDPicker | 全部 / 已通过 / 已拒绝 / 已撤回 |
| 数值卡片 | TDCard ×4 | 累计申请总额（年初至今）/ 当月笔数 / 已通过笔数 / 已通过金额。仅展示不可点击，空→¥0.00 |
| 趋势图 | fl_chart 双折线 | 近 12 月申请金额 vs 已通过金额对比。长按→tooltip（日期+数值），水平滑动 |
| 数据来源 | — | OA 本地 ExpenseApply 表，.NET 按角色+时间聚合 |

**角色差异**

| 角色 | 图表类型 | 明细列表 | 导出 |
|------|---------|:--:|:--:|
| 员工 | 个人双折线 | ❌ | ❌ |
| 经理 | 部门横向对比柱状图（每人两根柱：申请/已通过） | ✅ | ❌ |
| 财务/管理员 | 全公司趋势图 | ✅ | ✅ Excel |

---

#### 页面 23：费用报销明细报表 (`/report/expense-detail`)

**页面结构**

| 区域 | 组件 | 说明 |
|------|------|------|
| 时间筛选 | TDChip + TDDatePicker | 同页面 22 |
| 状态筛选 | TDPicker ×2 | 审批状态（全部/已通过/已拒绝）+ 付款状态（全部/待付款/已付款） |
| 数值卡片 | TDCard ×4 | 累计核销总额（年初至今）/ 当月笔数 / 待审批笔数 / 待付款笔数 |
| 趋势图 | fl_chart 双折线 | 近 12 月报销金额 vs 审批通过金额对比 |
| 数据来源 | — | OA 本地 Expense 表，审批状态通过 ERP 实时查询 |

**角色差异**

| 角色 | 图表类型 | 明细列表 | 导出 |
|------|---------|:--:|:--:|
| 员工 | 个人双折线 | ❌ | ❌ |
| 经理 | 部门横向对比柱状图 | ✅ | ❌ |
| 财务/管理员 | 全公司趋势图 | ✅ | ✅ Excel |

---

#### 页面 24：加班明细报表 (`/report/overtime-detail`)

**页面结构**

| 区域 | 组件 | 说明 |
|------|------|------|
| 时间筛选 | TDChip + TDDatePicker | 同页面 22 |
| 加班类型 | TDPicker | 全部 / 工作日 / 休息日 / 节假日 |
| 数值卡片 | TDCard ×4 | 当月净工时（h）/ 当月次数 / 累计调休小时 / 加班费结算次数 |
| 趋势图 | fl_chart 堆叠柱状图 | 近 12 月加班工时，按类型（工作日/休息日/节假日）分色堆叠 |
| 数据来源 | — | OA 本地 Overtime 表 |

**角色差异**

| 角色 | 图表类型 | 明细列表 | 导出 |
|------|---------|:--:|:--:|
| 员工 | 个人堆叠柱状图 | ❌ | ❌ |
| 经理 | 部门横向对比柱状图（每人汇总工时） | ✅ | ❌ |
| 财务/管理员 | 全公司趋势图 | ✅ | ✅ Excel |

---

#### 页面 25：用车明细报表 (`/report/vehicle-detail`)

**页面结构**

| 区域 | 组件 | 说明 |
|------|------|------|
| 时间筛选 | TDChip + TDDatePicker | 同页面 22 |
| 车辆筛选 | TDPicker | 全部 / 按车牌筛选 |
| 用途筛选 | TDPicker | 全部 / 接待 / 商务 / 公务 |
| 数值卡片 | TDCard ×4 | 当月次数 / 累计里程（km）/ 路桥停车费 / 未还车数量 |
| 趋势图 | fl_chart 双轴折线 | 近 12 月用车次数（左轴）vs 累计费用（右轴） |
| 数据来源 | — | OA 本地 Vehicle 表 |

**角色差异**

| 角色 | 图表类型 | 明细列表 | 导出 |
|------|---------|:--:|:--:|
| 员工 | 个人双轴折线 | ❌ | ❌ |
| 经理 | 部门横向对比柱状图（每人次数/费用） | ✅ | ❌ |
| 财务/管理员 | 全公司双轴折线 | ✅ | ✅ Excel |

---

#### 页面 26：外勤日志明细报表 (`/report/outing-log-detail`)

**页面结构**

| 区域 | 组件 | 说明 |
|------|------|------|
| 时间筛选 | TDChip + TDDatePicker | 同页面 22 |
| 客户筛选 | TDSearchBar | 输入客户名模糊搜索 |
| 数值卡片 | TDCard ×4 | 当月拜访次数 / 拜访客户数（去重）/ 平均评分（⭐）/ 未点评日志数 |
| 趋势图 | fl_chart 双轴折线 | 近 12 月拜访次数（左轴）vs 平均评分（右轴） |
| 数据来源 | — | OA 本地 OutingLog 表 LEFT JOIN OutingLogComment |

**角色差异**

| 角色 | 图表类型 | 明细列表 | 导出 |
|------|---------|:--:|:--:|
| 员工 | 个人双轴折线 | ❌ | ❌ |
| 经理 | 部门横向对比柱状图（每人次数/评分） | ✅ | ❌ |
| 财务/管理员 | 全公司双轴折线 | ✅ | ✅ Excel |

> **经理版交互增强**：点击柱状图柱体→下方列表联动过滤该员工本月单据→可穿透跳转对应详情页。适用于页面 22~26 所有报表。

---

## 6. 全局设计规范

#### 6.1 色彩体系

##### 品牌色梯度（主色 `#00ABF3` H=199° S=100%）

| Token | 亮色 | 深色 | 用途 |
|-------|------|------|------|
| `--primary-50` | `#E6F9FE` | `#0D2430` | 最浅背景 |
| `--primary-100` | `#B3EDFC` | `#103545` | Tag 浅底 |
| `--primary-500` | `#00ABF3` | `#00ABF3` | ★ 主色 |
| `--primary-600` | `#0089C4` | `#33BBF5` | 按压态 |
| `--primary-900` | `#002233` | `#CCEEFD` | 最深 |

##### 中性灰

| Token | 亮色 | 深色 | 用途 |
|-------|------|------|------|
| `--text-primary` | `#1F2937` | `#E6E6E6` | 标题（WCAG AAA） |
| `--text-secondary` | `#6B7280` | `#A6A6A6` | 正文（WCAG AA） |
| `--text-placeholder` | `#D1D6DB` | `#595959` | 占位符（WCAG AA） |
| `--bg-page` | `#F2F4F6` | `#0D0D0D` | 页面底 |
| `--bg-card` | `#FFFFFF` | `#1A1A1A` | 卡片/表单 |
| `--border` | `#E5E8EB` | `#333333` | 分割线 |

##### 语义色

| Token | 亮色 | 深色 | 用途 |
|-------|------|------|------|
| `--success` | `#0EA371` | `#22C55E` | 已通过/同意 |
| `--warning` | `#E37318` | `#F59E0B` | 审批中/警告 |
| `--danger` | `#DC2626` | `#EF4444` | 已拒绝/删除 |

##### 金额色（独立于语义色）

| Token | 亮色 | 深色 | 用途 |
|-------|------|------|------|
| `--amount-positive` | `#1D7AB5` | `#5BB8F0` | 正向金额（品牌蓝，区分 success） |
| `--amount-negative` | `#DC2626` | `#EF4444` | 负向金额/超支 |
| `--amount-neutral` | `#6B7280` | `#A6A6A6` | 普通数值 |

##### 信息色 / 支付状态

| Token | 亮色 | 深色 | 用途 |
|-------|------|------|------|
| `--info-text` | `#3B82C4` | `#6AA8E6` | 已还车/已付款 |
| `--paid-text` | `#2563B0` | `#6AA8E6` | 已付款文字 |
| `--unpaid-text` | `#CA8A04` | `#FBBF24` | 待付款文字 |
| `--withdrawn-text` | `#7C6F8A` | `#9A8DA0` | 已撤回文字 |
| `--chart-1~5` | 5 色 | 5 色 | fl_chart 图表 |

#### 6.2 字号层级

| Token | 字号 | 行高 | 用途 |
|-------|------|------|------|
| `--fs-title` | 18sp | 26px | 导航栏标题/详情大标题 |
| `--fs-subtitle` | 16sp | 24px | 卡片标题/表单分组/金额大字/按钮 |
| `--fs-body` | 14sp | 22px | 正文/列表摘要/输入框/Chip |
| `--fs-caption` | 12sp | 18px | 辅助说明/时间戳/状态标签/红点数 |

#### 6.3 间距系统

| Token | 值 | 用途 |
|-------|------|------|
| `--space-xs` | 4px | 图标与文字间距/Chip 内边距 |
| `--space-sm` | 8px | 列表项内 padding/表单行间距 |
| `--space-md` | 16px | 卡片内边距/页面左右边距 |
| `--space-lg` | 24px | 表单分组间距/卡片间距 |
| `--space-xl` | 32px | 页面顶部/底部留白 |

#### 6.4 空状态

| 场景 | 文案 |
|------|------|
| 消息列表为空 | "暂无消息通知" |
| 申请列表为空 | "暂无记录，点击下方按钮发起申请" |
| 审批列表为空 | "暂无待审批单据" |
| 报销明细为空 | "暂无报销明细，请先添加费用明细" |
| 照片墙为空 | "暂无附件" |
| 搜索无结果 | "未找到匹配的员工，试试其他关键词" |
| 公告列表为空 | "暂无行政公告" |
| 报表无数据 | "所选时间范围内暂无数据" |
| 导入费用申请为空 | "暂无可用的费用申请，请确认已有审批通过的申请" |
| 下属审批为空 | "暂无下属提交的审批单据" |
| 附件上传已满 | "上传数量已达上限" |
| 车辆池为空 | "暂无可用车辆，请联系管理员添加" |

#### 6.5 加载态

| 场景 | 方式 |
|------|------|
| 页面首次加载 | TDSkeleton 骨架屏（最长 8s 超时转网络异常态） |
| 下拉刷新 | RefreshIndicator |
| 上拉加载更多 | 底部菊花 + "加载中..." |
| 按钮提交中 | 按钮替换为 TDLoading 小菊花 + 禁用态 |
| 附件上传中 | 缩略图叠加半透明蒙层 + 中心菊花 |
| 一键导出中 | 圆形悬浮按钮旋转 + "导出中..." |

#### 6.6 网络异常态

| 场景 | 处理 |
|------|------|
| 列表加载失败 | TDEmpty + 断网图标 + `[点击重试]` |
| 提交失败 | TDToast（红）"提交失败，请稍后重试" |
| 接口超时（>15s） | TDEmpty + 时钟图标 + `[点击重试]`"请求超时" |
| 服务端 500 | TDToast"服务器繁忙，请稍后重试" |
| 401 未授权 | 静默触发宿主登录流程 |

#### 6.7 并发冲突

| 场景 | 处理 |
|------|------|
| 编辑草稿时单据已被他人提交 | `CONCURRENCY_CONFLICT`→TDDialog"已被修改或删除"→返回列表刷新 |
| 审批时单据已被他人处理 | `APPROVAL_CONFLICT`→详情页自动刷新+Toast"状态已更新" |

#### 6.8 全局交互

- **横竖屏**：仅支持竖屏
- **键盘**：聚焦时页面上移；金额类弹数字键盘；文本类弹默认键盘
- **返回手势**：iOS 边缘右滑；Android 系统返回键；表单有未保存修改时拦截确认
- **Deep Link**：`tboss://oa/{path}` → GoRouter 路由
- **字体缩放**：sp 单位，最小 11sp，导航栏标题不溢出
- **下拉刷新**：列表页顶部下拉触发 `RefreshIndicator`，重置 page=1 重新加载，刷新完成后短暂展示"已更新"提示

#### 6.9 圆角规范

| Token | 值 | 用途 |
|-------|------|------|
| `--radius-sm` | 4px | Chip / Tag / 小按钮 / 输入框 |
| `--radius-md` | 8px | 卡片 / 大按钮 / 列表项 |
| `--radius-lg` | 12px | 对话框 / 抽屉 / 底部弹出面板 |
| `--radius-round` | 999px | 胶囊按钮 / 头像 / 状态圆点 |

#### 6.10 阴影层级

| Token | 值 | 用途 |
|-------|------|------|
| `--shadow-none` | `none` | 平面/无阴影 |
| `--shadow-card` | `0 2px 8px rgba(0,0,0,0.08)` | 卡片/列表项 |
| `--shadow-fab` | `0 4px 16px rgba(0,0,0,0.12)` | 悬浮按钮/底部操作栏/导航栏 |
| `--shadow-drawer` | `-4px 0 20px rgba(0,0,0,0.15)` | 右侧抽屉/半屏面板 |

#### 6.11 动画时间规范

| Token | 值 | 用途 |
|-------|------|------|
| `--duration-fast` | 150ms | 按压反馈/hover 响应/Tag 关闭 |
| `--duration-normal` | 300ms | 页面过渡/图表数据刷新/ScrollTo 滚动/红闪消失 |
| `--duration-slow` | 500ms | 抽屉滑出/卡片展开收起/淡入淡出 |
| `--easing-standard` | `cubic-bezier(0.4, 0, 0.2, 1)` | 标准缓动曲线，用于所有过渡动画 |

> **应用示例**：图表刷新间隔统一用 `--duration-normal`（300ms）；表单校验失败红闪 2s 后淡出用 `--duration-slow`；按钮按下态用 `--duration-fast`；骨架屏超时阈值 8s（见 §6.5）。

#### 6.12 导航栏标题清单

| 页面 | 路由 | 标题 |
|------|------|------|
| 1 | `/` | —（Appshell） |
| 2 | `/messages` | "消息通知" |
| 3 | `/home` | "工作台" |
| 3.1 | `/profile` | "我的" |
| 3.2 | `/admin/permissions` | "权限管理" |
| 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 | `/announcement/create` | "发布公告" |
| 22 | `/report/expense-apply-detail` | "费用申请明细报表" |
| 23 | `/report/expense-detail` | "费用报销明细报表" |
| 24 | `/report/overtime-detail` | "加班明细报表" |
| 25 | `/report/vehicle-detail` | "用车明细报表" |
| 26 | `/report/outing-log-detail` | "外勤日志明细报表" |

#### 6.13 深色模式适配规范

##### 架构

三层主题体系，切换入口在"我的"→"深色模式" TDSwitch：

| 层 | 方法 | 文件 |
|---|------|------|
| **Material 主题** | `ThemeData(extensions: [AppColorsExtension.light/dark])` + `themeMode: themeMode` | `app_theme.dart`, `app.dart` |
| **TDesign 主题** | `TDThemeData` 注入 `ThemeData.extensions`，亮色用 `tdThemeData`，深色用 `darkTdThemeData` | `app_theme.dart` |
| **自定义 Widget** | `Theme.of(context).extension<AppColorsExtension>()!.xxx` | 全部 Widget 文件 |

##### 强制规则

1. **所有 UI 组件必须适配亮色和深色两种模式**，不允许只适配一种
2. 颜色统一从 `Theme.of(context).extension<AppColorsExtension>()!` 读取，禁止使用 `AppColors` / `AppDarkColors` 静态常量
3. TDesign 组件通过 `TDThemeData` 注入 `ThemeData.extensions` 自动适配，无需额外处理
4. 禁止 `const Color(0x...)` 硬编码——所有颜色必须在 `app_colors.dart` 中定义
5. 禁止 `Colors.white` / `Colors.black` 等系统常量用于背景/文字（可用于 `Colors.transparent`）
6. 新增颜色 Token 必须在 `AppColors` 和 `AppDarkColors` 中同时定义，深色值需满足：
   - 降低饱和度（saturation -10%~-20%）
   - 提高明度下限（lightness ≥ 40% 才可读）
   - 压缩对比度（最深底 `#0D0D0D`，不纯黑）

##### Widget 模板

```dart
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    return Container(
      color: colors.bgCard,
      child: Text('标题', style: TextStyle(color: colors.textPrimary)),
    );
  }
}
```

##### 工厂 / 静态方法（无 BuildContext）

允许使用 `AppColors.xxx`，但必须确保调用方传入 `colors` 参数或在 `build` 中覆写：

```dart
// ✅ 正确 - 工厂方法接受 colors 参数
factory StatusTag.fromStatus(String status, AppColorsExtension colors) {
  return StatusTag(backgroundColor: colors.successBg, textColor: colors.success);
}

// ❌ 错误 - 硬编码 AppColors 无法切换深色
factory StatusTag.fromStatus(String status) {
  return StatusTag(backgroundColor: AppColors.successBg, ...);
}
```

---

> **文档版本**：v2.0 | 日期：2026-06-05 | 变更：新增深色模式色板 + 适配规范
