# 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 路由 |
